Commit Graph

6 Commits

Author SHA1 Message Date
Jordan Ritter 8714ab569b chore(showcase): apply oxfmt formatting across showcase scripts and shells
oxfmt --write normalized formatting on showcase scripts, the four shells, and the
new oxlint rule; required for the repo-root oxfmt --check CI gate.
2026-05-29 11:45:16 -07:00
Jordan Ritter 7284ed3d84 fix(showcase): guard redeploy-summary download against legit no-redeploy + harden env lint rule
D1 — showcase_deploy.yml false-red fix
======================================

The build workflow legitimately uploads no `redeploy-summary` artifact when it ran
(push touched `showcase/**` so `paths:` matched) but `detect-changes` found no
buildable service, so `redeploy-staging` was skipped. The build still concludes
`success`, so `showcase_deploy.yml` fires on `workflow_run` and `resolve-matrix`
runs. `actions/download-artifact@v4` with `name:` HARD-FAILS on a missing
artifact, so the unguarded download was failing the job, and a downstream guard
that trips `enforce-redeploy-gate` on `resolve-matrix.result == 'failure'` was
flipping the workflow RED — a false-red on a routine showcase-docs/script change.

Add an artifact-existence pre-check using `actions/github-script` (pinned by SHA,
matching the existing repo convention) that lists the artifacts for
`workflow_run.id` via `actions: read` (already granted to `resolve-matrix`) and
sets `summary_present=true|false`. Gate the existing download step on
`summary_present == 'true'`. Keep NO `continue-on-error`, so the C1 property
holds: when the artifact exists but the download genuinely fails, the job still
fails loud and `enforce-redeploy-gate` correctly reds the workflow. When the
artifact is legitimately absent, the bash gate's existing `[ ! -f "$SUMMARY" ]`
branch no-ops (`redeploy_red=false`, `ok_services=""`) — nothing was
redeployed, so there is nothing to gate.

Updated the step comment block to enumerate the three distinct cases now
handled: workflow_dispatch (no download); workflow_run + artifact absent
(graceful skip); workflow_run + artifact present (download with fail-loud).

L1-L5 — env lint rule hardening
===============================

- L1: route the destructuring (VariableDeclarator/ObjectPattern) branch through
  the shared `staticKeyName()` helper so the computed-string-key form
  `const { ["NEXT_PUBLIC_X"]: y } = process.env` and the no-expression
  template-literal form `const { [\`NEXT_PUBLIC_X\`]: y } = process.env` are
  caught with the same parity as the bracket-member read.
- L2: unwrap a wrapping `ChainExpression` at the top of `isProcessEnv()` so
  `process.env?.X` is matched robustly across parser flavors; corrected the
  helper's doc comment to describe the actual semantics.
- L3: export `BANNED_KEYS` from the rule module and have the table-driven test
  dynamically import the rule's own Set instead of hand-mirroring it — the
  test set now cannot drift from the rule.
- L4: added override-scoping fixtures for `showcase/shell/src/**` and
  `showcase/shell-dojo/src/**`; the `.oxlintrc.json` override list already
  includes these, but the test now exercises them so an accidental drop is
  caught.
- L5: expanded the file-header "Out of scope" doc list to include bulk-iteration
  reads (`Object.keys/values/entries(process.env)`, for-in, spread
  `{...process.env}`), rest-pattern destructuring, compound-assignment LHS, and
  update operators. Documentation-only — the deliberate non-coverage is now
  auditable.

Validation
==========

- RED→GREEN confirmed for L1 (two new destructuring computed-key tests) and L3
  (dynamic `await import(...)` of BANNED_KEYS failed pre-fix with
  "Rule module did not export a non-empty BANNED_KEYS Set", green after export).
- vitest: 38 passed (was 34 baseline + 4 new); aggregate-build-results 6 passed.
- Ruby promote suite: 87 runs, 251 assertions, 0 failures (unchanged).
- python3 yaml.safe_load: showcase_deploy.yml + showcase_build.yml +
  showcase_promote.yml all parse OK.
- actionlint: zero NEW findings on the changed file. The pre-existing
  showcase_build.yml SC2086/SC2129/runner-label findings are identical on the
  integration baseline (unchanged by this commit).
2026-05-29 11:45:14 -07:00
Jordan Ritter a6239cde11 fix(showcase): close deploy-gate false-greens and broaden public-env lint rule
Seven-agent CR surfaced correctness defects in the build/deploy/promote
pipeline and in the no-public-env-shell-read oxlint rule. This commit
closes the false-green paths and broadens lint coverage.

Workflow fixes:
- showcase_deploy.yml: drop `continue-on-error: true` on the redeploy-summary
  artifact download. The dispatch path is already guarded by the `if:
  workflow_run` clause, so the bash "no summary" branch handles legitimate
  manual dispatches. A genuine workflow_run download failure must now fail
  loud instead of silently widening verify to the full service set against
  stale `:latest`.
- showcase_build.yml: redeploy-staging now intersects the build matrix with
  the aggregator success set (`needs.aggregate-build-results.outputs.results`,
  status == "success") before producing the redeploy CSV. Failed/skipped
  slots no longer get redeployed (which would just re-pull stale `:latest`
  and look healthy).
- showcase_build.yml: `notify-all-builds-failed` now additionally requires
  `needs.build.result == 'failure'` so it doesn't Slack-spam when the build
  job was SKIPPED (verify-image-refs upstream failure).
- showcase_build.yml: `notify` now lists [build, aggregate-build-results,
  redeploy-staging] in `needs:` so aggregator/redeploy failures still emit
  a Slack signal. `if: failure()` still skips when none of the needs failed.
- showcase_build.yml: `set -euo pipefail` on the Prepare build args step
  so a transient $GITHUB_OUTPUT write failure can't ship images without
  COMMIT_SHA/BRANCH baked in.
- showcase_deploy.yml: `enforce-redeploy-gate` now also trips on a
  resolve-matrix failure (`needs.resolve-matrix.result == 'failure'`) so
  an upstream crash that leaves `redeploy_red` empty can't bypass the gate.
- Doc-comment accuracy: drop stale `(PR #5093)` reference; correct the
  env-IDs source-of-truth comment; document the optional `skip_build` field
  in ALL_SERVICES; clarify that health_path is informational and verify
  uses per-service drivers; add the missing `resolve-targets` step 0 to the
  promote workflow's "Order:" header.

Aggregator fix (RED-GREEN):
- aggregate-build-results.ts: throw on zero slot dirs. The job is gated
  upstream on has_changes == 'true', so zero slot dirs is a broken artifact
  download, not a legitimate empty build set. Silently emitting
  any_success=false + results=[] is indistinguishable from "all builds
  failed" and lets the deploy workflow fall back to probing the full
  service set against stale `:latest`. Refuse the ambiguity.
- aggregate-build-results.test.ts: existing empty-INPUT_DIR test was
  updated to assert the throw (was: return []).

Oxlint rule (RED-GREEN):
- no-public-env-shell-read.mjs: handle destructuring reads
  (const { NEXT_PUBLIC_X } = process.env and aliased form), template-literal
  computed keys (process.env[\`NEXT_PUBLIC_X\`]), and explicitly skip
  assignment-LHS / `delete` targets (writes are not reads). Optional
  chaining already worked through the existing MemberExpression path.
  Aliasing (`const e = process.env; e.X`) is intentionally documented as
  out of scope (needs scope tracking). Description sharpened to say the
  rule guards a specific banned-key set, not all NEXT_PUBLIC_* reads.
- .oxlintrc.json: tighten the off-override glob from
  `showcase/**/*runtime-config*` to
  `showcase/**/lib/runtime-config*.{ts,tsx}` so it only silences the
  intended implementation files, not arbitrary paths containing that
  substring.
- lint-rule-no-public-env.test.ts: rewritten as table-driven coverage of
  every BANNED_KEYS entry (dotted + bracket-string forms), every ALLOWED
  key (asserting non-firing), all new variants from the rule expansion,
  the assignment/delete non-fire cases, and override scoping
  (runtime-config exempt; packages exempt; shell-tree non-runtime-config
  flagged).

Validation:
- actionlint on all three workflows: 8 pre-existing findings (depot label,
  pre-existing SC2086 infos in untouched steps); my edits add zero.
- python3 yaml.safe_load: all three workflows OK.
- vitest aggregate-build-results.test.ts: 6/6 pass (incl. new throw test).
- vitest lint-rule-no-public-env.test.ts: 34/34 pass.
- vitest full showcase/scripts suite: 1654/1654 pass across 46 files.
- ruby showcase/bin/spec/all_tests.rb: 87 runs, 0 failures.
- Intersection jq proof (matrix a,b,c × success a,c) → "a,c"; all-failed
  → ""; skipped status excluded.
2026-05-29 11:45:14 -07:00
Jordan Ritter 73e4d29443 feat(showcase): add oxlint guard against NEXT_PUBLIC_* shell reads
Plan-B / Option-B migration moved every shell URL/analytics key off the
build-time NEXT_PUBLIC_* env channel and onto runtime config served via
__SHOWCASE_CONFIG__ + getRuntimeConfig(). To prevent a silent regression
where a future change reintroduces a direct process.env.NEXT_PUBLIC_*
read in shell code (which would re-freeze the value at build time and
break no-rebuild env switching), add a focused lint rule.

The rule (copilotkit/no-public-env-shell-read) is implemented as a
custom oxlint JS plugin rule in the existing copilotkit plugin and
enabled under shell-scoped overrides in .oxlintrc.json:

- Errors on process.env.NEXT_PUBLIC_<URL/ANALYTICS> reads in:
  showcase/shell-dashboard/src/**, showcase/shell-docs/src/**,
  showcase/shell/src/**, showcase/shell-dojo/src/**
- Banned keys: POCKETBASE_URL, SHELL_URL, BASE_URL, OPS_BASE_URL,
  INTELLIGENCE_SIGNUP_URL, POSTHOG_KEY, POSTHOG_HOST, SCARF_PIXEL_ID,
  GOOGLE_ANALYTICS_TRACKING_ID, REB2B_KEY, REO_KEY
- Intentionally allowed (NOT banned): NEXT_PUBLIC_COMMIT_SHA and
  NEXT_PUBLIC_BRANCH (build-stamped artifact identifiers per B10/B11)
  and NEXT_PUBLIC_LOCAL_BACKENDS (computed from shared/local-ports.json
  at build, local-dev only).
- Excluded files (rule disabled via a follow-up override): MDX content
  under shell-docs/src/content/**, runtime-config implementation files,
  and *.test.{ts,tsx} / *.spec.{ts,tsx}. oxlint does not support
  excludedFiles inside an override block, so the exclusion is expressed
  as a later override that sets the rule to off.

Plan-B originally targeted oxlint's eslint/no-restricted-syntax with an
AST-selector regex. oxlint 1.x does not implement that rule (only
no-restricted-globals / no-restricted-imports), so the equivalent guard
is realized as a small custom rule in the existing copilotkit JS plugin
(meta.name=copilotkit), reusing the same plugin loader the repo already
has for require-cpk-prefix and no-single-arg-zod-record.

Verification (red-green): the rule fires on a fixture containing
process.env.NEXT_PUBLIC_POCKETBASE_URL and does NOT fire on a fixture
containing process.env.NEXT_PUBLIC_COMMIT_SHA. Test pins the config via
-c so it works inside git worktrees nested under .claude/worktrees/
where oxlint's automatic upward config search can miss the worktree's
own .oxlintrc.json.

All four shells lint clean: 0 errors of the new rule across
shell-dashboard (114 files), shell-docs (137), shell (29), shell-dojo (6).
2026-05-29 11:45:08 -07:00
Mark Fogle 70f54a8403 fix: use two-argument z.record for Zod 4 compatibility, add lint guard
Zod 4 made the key schema mandatory for z.record, so the single-argument
z.record(valueType) form is a compile-time error (TS2554) when built against
Zod 4. @copilotkit/react-core declares zod ">=3.0.0", so downstream apps on
Zod 4 are affected; runtime parsing is unaffected under both majors.

- react-core + vue MCPAppsActivityContentSchema: toolInput now uses the
  two-argument z.record(z.string(), z.unknown()) form
- react-core defineToolCallRenderer test: same fix for a metadata schema
- add a toolInput field-contract test (round-trips mixed value types)
- add copilotkit/no-single-arg-zod-record oxlint rule (autofix), enabled as
  error for packages/**; the incompatibility is type-level, so no runtime
  test can guard it while the workspace lockfile pins Zod 3

Closes #4295

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-26 21:33:55 +00:00
Alem Tuzlak 79ce60c580 chore: migrate from eslint+prettier to oxlint+oxfmt
Replace eslint and prettier with oxlint and oxfmt for faster linting
and formatting across the monorepo. Remove all eslint and prettier
configs, dependencies, and related packages. Add .oxlintrc.json and
.oxfmtrc.json for the new tooling. Update CI workflows and lefthook
hooks accordingly. Reformat codebase with oxfmt.

https://claude.ai/code/session_01GMkSf29p78HuMR1mbXn8He
2026-04-02 16:39:05 +02:00