Skip to content

fix(spec): regenerate drifted skill references + react-blocks contract, and gate them in CI - #3138

Merged
os-zhuang merged 2 commits into
mainfrom
claude/sweet-ardinghelli-731802
Jul 17, 2026
Merged

fix(spec): regenerate drifted skill references + react-blocks contract, and gate them in CI#3138
os-zhuang merged 2 commits into
mainfrom
claude/sweet-ardinghelli-731802

Conversation

@os-zhuang

Copy link
Copy Markdown
Contributor

Two committed generated surfaces under skills/ were drifted from packages/spec and completely unguarded by CI. Same class as #3134, but on surfaces that ship to third parties via npx skills add objectstack-ai/framework — so the drift is served straight to consumers' agents.

The drift was real, and user-facing

An independent oracle (parses every pointer out of the committed indexes, asserts the file exists — deliberately not the generator, so it isn't circular):

pointersbroken
main @ ce468c8321136
this PR1260

The six dangling pointers handed agents paths to files that don't exist:

Every change in the regenerated diff traces to a spec change; I checked each one. The encryption.zod.ts drops are because field.zod.ts no longer imports it; the metadata-protection / protection / visibility / public-auth-features additions are new spec imports (ADR-0010, ADR-0089, #2874); the flow.zod.ts and manifest.zod.ts description changes are edited JSDoc. Output is byte-identical across runs.

Regenerating alone would have shipped a regression

SKILL_MAP still named data/dataset.zod.ts, and the generator warned and skipped the missing file. A plain regen would therefore have quietly dropped seed coverage from objectstack-data — whose own SKILL.md advertises defineSeed() authoring — and the new gate would have locked that in as "correct".

So: point the map at data/seed.zod.ts, and make a dangling entry a hard failure in both modes. The map is authored config; a dangling entry is always a bug in it, never a spec change to absorb. That warn-and-continue is the same false-green vector the gate exists to close.

Gate mechanics

Follows #3134's build-docs.ts: every write goes through emit(), every wholesale-regenerated folder through manageDir(), nothing touches disk until flush() — so write and --check share all generation logic and differ only in disposition. Extracted to scripts/lib/generated-output.ts rather than copy-pasted a third time.

manageDir() takes an ownership predicate, because a skill's references/ folder is only partially generated: _index.md is ours, but react-blocks.md (sibling generator) and hand-written notes must survive. Verified they do.

New scripts: check:skill-refs, check:react-blocks.

Proven red before being trusted (13/13)

A gate that cannot fail is worse than no gate. Each was reproduced going red, then green again after restore:

caseresult
stale content✅ exit 1 ~ out of date
missing file✅ exit 1 + missing
stale untracked leftover (subfolder + loose .zod.ts)✅ exit 1 - stale
↳ same state under git diff --exit-codeexit 0 — blind. This is why manageDir exists
dangling SKILL_MAP entry (the bug that shipped)✅ exit 1, in check and write mode
skill dir missing from disk✅ exit 1
spec change the contract hadn't absorbed✅ exit 1 ~ out of date
react-blocks: schemas fail to load (swallowed z.toJSONSchema error)✅ exit 1 — and write mode refused to clobber the good contract

That last guard is why it checks data-kind props rather than props.length: mergeProps puts the hand-authored overlay first, so a block whose schema failed to load still reports props. The data props are what vanish.

Placement: lint.yml, deliberately

In lint.yml's "TypeScript Type Check" (no paths filter + required), not ci.yml's check-generated. That job is filter-gated on:

skilldocs:
- 'skills/**'
- 'content/docs/guides/skills.mdx'
- 'packages/spec/scripts/build-skill-docs.ts'

notpackages/spec/src/**. It would skip exactly the spec-only PRs that cause this drift (the #3076 failure mode), and it isn't a required check, so it could be merged past even when it does run. Required checks confirmed as ["TypeScript Type Check","Build Core","Test Core","Dogfood Regression Gate"].

Both gates need no ^build — verified empirically by removing all 23 workspace dist/ dirs and re-running (turbo's ^build on gen:skill-refs is vestigial; pnpm --filter bypasses turbo anyway). So they run before the workspace build and fail in seconds.

Note on #3134

#3134 is open and touches lint.yml + packages/spec/package.json too, inserting check:docs in the same step gap. Expect a trivial adjacent-block conflict; both blocks coexist. I based on main rather than on that branch so this stands alone. Follow-up once it lands: migrate build-docs.ts onto scripts/lib/generated-output.ts.

check:react-conformance was checked for overlap — there is none. It compares spec schemas against the objectui frontend manifest (opt-in via MANIFEST=, exits 0 without it) and never checks generator output against committed files.

Verification

  • check:skill-refs / check:react-blocks green on a clean tree; 13/13 red-proofs pass
  • tsc --noEmit clean · spec tests 254 files / 6890 passed
  • oracle: 126 pointers, 0 broken · generators deterministic across 3 runs

🤖 Generated with Claude Code

…t, and gate them in CI
skills/*/references/_index.md and the objectstack-ui react-blocks contract are
generated from packages/spec/src and committed, but nothing regenerated them in
CI — no workflow referenced either generator, the spec `build` script does not
run them, and neither had a `check:` mode. They drifted. Unlike the reference
docs (#3134), these ship to third parties via `npx skills add`, so the drift is
served straight to consumers' agents.
An independent oracle over the committed indexes: 6 of 113 schema pointers named
files that no longer exist — `automation/workflow.zod.ts` (deleted by #1398),
`system/masking.zod.ts` (deleted), `data/dataset.zod.ts` (renamed to
`data/seed.zod.ts` by #1620). After this change: 126 pointers, 0 broken.
Regenerating alone was not enough. SKILL_MAP still named `data/dataset.zod.ts`,
and the generator warned-and-skipped on the missing file, so a plain regen would
have quietly dropped seed coverage from objectstack-data — whose own SKILL.md
advertises `defineSeed()` authoring — and the new gate would then have locked
that in as correct. Point the map at `data/seed.zod.ts` and make a dangling entry
a hard failure in both modes: the map is authored config, so a dangling entry is
always a bug in it, never a spec change to absorb.
Gate mechanics follow build-docs.ts (#3134): every write goes through `emit()`,
every wholesale-regenerated folder through `manageDir()`, and nothing touches
disk until `flush()` — so write and --check share all generation logic and
differ only in disposition. Extracted to scripts/lib/generated-output.ts rather
than copy-pasted. `manageDir()` takes an ownership predicate because a skill's
references/ folder is only partially generated: `_index.md` is ours, but
`react-blocks.md` (sibling generator) and hand-written notes must survive.
Placed in lint.yml's "TypeScript Type Check", not ci.yml's check-generated job.
That job is paths-filtered on skills/** and build-skill-docs.ts but NOT on
packages/spec/src/**, so it would skip exactly the spec-only PRs that cause this
drift — and it is not a required check, so it could be merged past anyway. The
type-check job has no paths filter and IS required. Both gates read src/ via tsx
and need no build (verified with every workspace dist/ removed), so they run
before the workspace build and fail in seconds.
Each gate was proven to go red before being trusted (13/13): stale content,
missing file, stale untracked leftover, dangling SKILL_MAP entry, missing skill
dir, and a spec change the contract had not absorbed. The stale-leftover case is
why manageDir exists — `git diff --exit-code` returns 0 on it.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@os-zhuangos-zhuang added ci/cd tooling skip-changeset PR has no user-facing published change; bypasses the changeset gate labels Jul 17, 2026
@vercel

vercelBot commented Jul 17, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

ProjectDeploymentActionsUpdated (UTC)
specReadyReadyPreview, CommentJul 17, 2026 2:12pm

Request Review

@github-actionsgithub-actionsBot added documentation Improvements or additions to documentation dependencies Pull requests that update a dependency file size/m labels Jul 17, 2026
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 1 package(s): @objectstack/spec.

102 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:

  • content/docs/ai/agents.mdx(via @objectstack/spec)
  • content/docs/ai/skills-reference.mdx(via @objectstack/spec)
  • content/docs/ai/skills.mdx(via @objectstack/spec)
  • content/docs/api/client-sdk.mdx(via @objectstack/spec)
  • content/docs/api/environment-routing.mdx(via @objectstack/spec)
  • content/docs/api/error-catalog.mdx(via @objectstack/spec)
  • content/docs/api/error-handling-client.mdx(via @objectstack/spec)
  • content/docs/api/error-handling-server.mdx(via @objectstack/spec)
  • content/docs/api/index.mdx(via @objectstack/spec)
  • content/docs/automation/approvals.mdx(via packages/spec)
  • content/docs/automation/flows.mdx(via @objectstack/spec)
  • content/docs/automation/hook-bodies.mdx(via packages/spec)
  • content/docs/automation/hooks.mdx(via @objectstack/spec)
  • content/docs/automation/index.mdx(via @objectstack/spec)
  • content/docs/automation/webhooks.mdx(via @objectstack/spec)
  • content/docs/automation/workflows.mdx(via @objectstack/spec)
  • content/docs/concepts/architecture.mdx(via @objectstack/spec)
  • content/docs/concepts/design-principles.mdx(via packages/spec)
  • content/docs/concepts/index.mdx(via @objectstack/spec)
  • content/docs/concepts/metadata-driven.mdx(via @objectstack/spec)
  • content/docs/concepts/metadata-lifecycle.mdx(via packages/spec)
  • content/docs/concepts/north-star.mdx(via packages/spec)
  • content/docs/data-modeling/analytics.mdx(via @objectstack/spec)
  • content/docs/data-modeling/drivers.mdx(via @objectstack/spec)
  • content/docs/data-modeling/external-datasources.mdx(via @objectstack/spec)
  • content/docs/data-modeling/field-types.mdx(via @objectstack/spec)
  • content/docs/data-modeling/fields.mdx(via @objectstack/spec)
  • content/docs/data-modeling/formulas.mdx(via @objectstack/spec)
  • content/docs/data-modeling/index.mdx(via @objectstack/spec)
  • content/docs/data-modeling/objects.mdx(via @objectstack/spec)
  • content/docs/data-modeling/queries.mdx(via @objectstack/spec)
  • content/docs/data-modeling/schema-design.mdx(via @objectstack/spec)
  • content/docs/data-modeling/seed-data.mdx(via @objectstack/spec)
  • content/docs/data-modeling/validation-rules.mdx(via @objectstack/spec)
  • content/docs/data-modeling/validation.mdx(via @objectstack/spec)
  • content/docs/deployment/troubleshooting.mdx(via @objectstack/spec)
  • content/docs/getting-started/build-with-claude-code.mdx(via @objectstack/spec)
  • content/docs/getting-started/cli.mdx(via @objectstack/spec)
  • content/docs/getting-started/common-patterns.mdx(via @objectstack/spec)
  • content/docs/getting-started/examples.mdx(via @objectstack/spec)
  • content/docs/getting-started/quick-reference.mdx(via @objectstack/spec)
  • content/docs/getting-started/quick-start.mdx(via @objectstack/spec)
  • content/docs/getting-started/validating-metadata.mdx(via @objectstack/spec)
  • content/docs/getting-started/your-first-project.mdx(via @objectstack/spec)
  • content/docs/kernel/cluster.mdx(via @objectstack/spec)
  • content/docs/kernel/contracts/auth-service.mdx(via packages/spec)
  • content/docs/kernel/contracts/cache-service.mdx(via packages/spec)
  • content/docs/kernel/contracts/data-engine.mdx(via @objectstack/spec)
  • content/docs/kernel/contracts/index.mdx(via @objectstack/spec)
  • content/docs/kernel/contracts/metadata-service.mdx(via packages/spec)
  • content/docs/kernel/contracts/storage-service.mdx(via packages/spec)
  • content/docs/kernel/index.mdx(via packages/spec)
  • content/docs/kernel/runtime-services/email-service.mdx(via packages/spec)
  • content/docs/kernel/runtime-services/index.mdx(via packages/spec)
  • content/docs/kernel/runtime-services/queue-service.mdx(via packages/spec)
  • content/docs/kernel/runtime-services/sharing-service.mdx(via packages/spec)
  • content/docs/kernel/runtime-services/sms-service.mdx(via packages/spec)
  • content/docs/kernel/runtime-services/storage-service.mdx(via packages/spec)
  • content/docs/kernel/services-checklist.mdx(via @objectstack/spec)
  • content/docs/permissions/authorization.mdx(via @objectstack/spec)
  • content/docs/permissions/permission-sets.mdx(via @objectstack/spec)
  • content/docs/permissions/permissions-matrix.mdx(via @objectstack/spec)
  • content/docs/permissions/positions.mdx(via @objectstack/spec)
  • content/docs/permissions/rls.mdx(via @objectstack/spec)
  • content/docs/permissions/sharing-rules.mdx(via @objectstack/spec)
  • content/docs/plugins/adding-a-metadata-type.mdx(via @objectstack/spec)
  • content/docs/plugins/development.mdx(via @objectstack/spec)
  • content/docs/plugins/index.mdx(via @objectstack/spec)
  • content/docs/plugins/packages.mdx(via @objectstack/spec)
  • content/docs/protocol/backward-compatibility.mdx(via @objectstack/spec)
  • content/docs/protocol/diagram.mdx(via packages/spec)
  • content/docs/protocol/kernel/config-resolution.mdx(via @objectstack/spec)
  • content/docs/protocol/kernel/i18n-standard.mdx(via @objectstack/spec)
  • content/docs/protocol/kernel/lifecycle.mdx(via @objectstack/spec)
  • content/docs/protocol/kernel/plugin-spec.mdx(via @objectstack/spec)
  • content/docs/protocol/kernel/runtime-capabilities.mdx(via @objectstack/spec)
  • content/docs/protocol/knowledge.mdx(via @objectstack/spec)
  • content/docs/protocol/objectql/index.mdx(via packages/spec)
  • content/docs/protocol/objectql/query-syntax.mdx(via @objectstack/spec)
  • content/docs/protocol/objectql/schema.mdx(via @objectstack/spec)
  • content/docs/protocol/objectql/security.mdx(via packages/spec)
  • content/docs/protocol/objectql/state-machine.mdx(via @objectstack/spec)
  • content/docs/protocol/objectui/actions.mdx(via @objectstack/spec)
  • content/docs/protocol/objectui/concept.mdx(via @objectstack/spec)
  • content/docs/protocol/objectui/index.mdx(via @objectstack/spec)
  • content/docs/protocol/objectui/layout-dsl.mdx(via @objectstack/spec)
  • content/docs/protocol/objectui/record-alert.mdx(via @objectstack/spec)
  • content/docs/protocol/objectui/widget-contract.mdx(via @objectstack/spec)
  • content/docs/releases/implementation-status.mdx(via @objectstack/spec)
  • content/docs/releases/index.mdx(via @objectstack/spec)
  • content/docs/releases/v12.mdx(via @objectstack/spec)
  • content/docs/releases/v13.mdx(via @objectstack/spec)
  • content/docs/releases/v9.mdx(via @objectstack/spec)
  • content/docs/ui/actions.mdx(via @objectstack/spec)
  • content/docs/ui/create-vs-edit-form.mdx(via @objectstack/spec)
  • content/docs/ui/dashboards.mdx(via @objectstack/spec)
  • content/docs/ui/forms.mdx(via @objectstack/spec)
  • content/docs/ui/index.mdx(via @objectstack/spec)
  • content/docs/ui/public-data-collection.mdx(via @objectstack/spec)
  • content/docs/ui/setup-app.mdx(via @objectstack/spec)
  • content/docs/ui/translations.mdx(via @objectstack/spec)
  • content/docs/ui/views.mdx(via @objectstack/spec)

Advisory only. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs origin/main → pass the list as args.docs.

…lli-731802
# Conflicts:
#	.github/workflows/lint.yml
@os-zhuang

Copy link
Copy Markdown
ContributorAuthor

Merged main in (no force-push, per AGENTS.md §3). #3134 landed while this was in review, and as predicted it conflicted in lint.yml — a trivial adjacent-block one. Resolved by keeping both gates; they're the same class on different surfaces:

- Type check (@objectstack/spec)
- Check generated reference docs are in sync with the spec # check:docs (#3134)
- Check generated skill references are in sync with the spec # check:skill-refs (this PR)
- Check the react-blocks contract is in sync with the spec # check:react-blocks (this PR)
- Build workspace packages

I also re-checked my placement argument against the post-#3134 filter rather than the one I originally read, and updated the comment to match. #3134 renamed the job to check-generated and added spec paths to the filter — but only migrations/**, conversions/**, and protocol-version.ts. No schema dirs. So a PR touching src/data/** or src/ui/** — exactly what drives these two artifacts — still never triggers that job, and it's still not required. lint.yml remains the correct home.

Re-verified against the merged tree:

Follow-up from the merge, worth doing now that #3134 is in: build-docs.ts carries its own inline copy of emit()/manageDir(), while this PR extracted the same logic to scripts/lib/generated-output.ts. Migrating it onto the shared helper would leave one implementation of the convention instead of two.

@os-zhuang
os-zhuang merged commit 6cc73e3 into mainJul 17, 2026
17 of 18 checks passed
@os-zhuang
os-zhuang deleted the claude/sweet-ardinghelli-731802 branch July 17, 2026 13:33
os-zhuang added a commit that referenced this pull request Jul 17, 2026
#3138 extracted the emit()/manageDir()/flush() sink to lib/generated-output.ts and moved
build-skill-references + build-react-blocks-contract onto it, but left build-docs.ts
carrying the inline original — #3126 was rewriting the same file and the migration would
have conflicted. #3126 has landed, so collect the debt.
Two copies of a check/write sink is the one duplication this design cannot afford: its
whole claim is that --check and write are the same code, and that claim is per copy. A
fix to one silently leaves the other behind, and the failure mode is a gate that passes
on output a real run would not produce.
Adds wasEmitted() to the shared sink. build-docs is the first caller whose later output
depends on its earlier output — each category index links only the pages that got
generated — and that question belongs to the sink, which already owns the emitted map.
New export; the other two callers are unaffected. The empty-input guard moves to
flush()'s guard hook, same meaning.
Pure refactor, verified: byte-identical output across all 258 files; all five drift
classes still fail (stale content, missing page, stale leftover, no-schema guard,
in-sync green); both existing sink callers regress clean, including #3138's reverse test
that a hand-written .md in skills/*/references/ keeps the gate green and survives a write.
Net -58 lines.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
os-zhuang added a commit that referenced this pull request Jul 17, 2026
PROTOCOL_MAP.md is hand-written and nothing regenerates it, so renames
orphan rows silently. 25 of its 132 `src/…` links pointed at files that no
longer exist.
The reported row was misdiagnosed, and the git history says why. The prose
"Virtual capabilities for analytics datasets" was NOT a half-finished
rewrite left by #1620 — it has been there verbatim since the map was created
(ced379b), when `data/dataset.zod.ts` held the *seed* schema
(object/externalId/mode/records). It never described that file. So this is
one wrong row plus one absent row, not one half-updated row:
- `data/dataset.zod.ts` -> `data/seed.zod.ts`, described as seed fixtures
(renamed by #1620 to free `dataset` for analytics, ADR-0021).
- the analytics dataset the old prose was reaching for is a real, unlisted
schema: added as `ui/dataset.zod.ts` (ADR-0021 semantic layer).
The sweep found the same rot everywhere, all from deliberate refactors that
left the map behind:
- `src/hub/**` (9 rows) — the directory no longer exists (142703d).
Survivors rehomed: license/registry-config/tenant -> src/system,
plugin-registry/plugin-security -> src/kernel; composer, marketplace,
space, hub-federation and api/hub were deleted outright. Section 9 is
therefore gone and Kernel renumbered 10 -> 9.
- `src/ai` (10 rows) — narrowed by 629a716. Repointed where a successor
exists (agent-action -> tool, cost -> usage, rag-pipeline ->
knowledge-source); dropped nlq/orchestration/predictive/devops-agent/
feedback-loop/plugin-development/runtime-ops.
- `identity/role` -> `identity/position` (ADR-0090 D3 renamed sys_role).
- `security/policy` (#2387), `system/compliance` + `system/masking`
(ADR-0056 D8) — removed as dead, rows dropped.
Gate: scripts/protocol-map.test.ts asserts every link resolves. Proven to go
red first — it listed exactly the 25 dead links before the fix. It rides Test
Core (required); `core: packages/**` covers the file and turbo's
$TURBO_DEFAULT$ inputs include it, so a map-only edit still re-runs the test
rather than cache-hitting to a false green. A new workflow job would not have
blocked merges (main requires only 4 checks), and check-links.yml is dormant
(workflow_dispatch only) and does not cover packages/spec.
Deliberately asserts links only, not completeness: the map is a curated
digest, not a generated index.
Sibling of #3138, which fixed the identical stale `data/dataset.zod.ts`
pointer in build-skill-references.ts's SKILL_MAP. That one guards generated
artifacts that ship to third parties; PROTOCOL_MAP.md is hand-written and
repo-internal (not in the package's npm `files`), so it needs its own check.
Co-authored-by: Jack Zhuang <277994282+os-zhuang@users.noreply.github.com>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
os-zhuang added a commit that referenced this pull request Jul 18, 2026
…lver (#3139) (#3155)
`extractLocalImports` in build-skill-references.ts under-reported a skill's
transitive schema deps because of two blind spots in the import scan:
- The regex `/^import\s+.*\s+from\s+.../` used `.`, which does not cross
newlines, so any multi-line named import (`import {\n … \n} from './x'`)
was silently dropped and never queued into the closure.
- `.js`-suffixed ESM specifiers were mangled: the resolver appended `.ts`
unconditionally, turning `./types.js` into a non-existent `./types.js.ts`
that then failed the `fs.existsSync` check and was discarded.
Replace the pattern with a multi-line-tolerant scan that excludes `;`,
quotes, and `(` between `import` and `from` — this keeps the non-greedy
span from bridging across statement boundaries, side-effect imports, or a
dynamic `import(...)`. Normalize `.js` specifiers to `.ts` before resolving.
Regenerated the indexes with the fix in place: the platform skill now
surfaces `system/tenant.zod.ts`, reached through the previously-invisible
`.js` import in `kernel/context.zod.ts`. The `check:skill-refs` gate added
in #3138 passes with the regenerated output.
Claude-Session: https://claude.ai/code/session_01YTjm38Bo2xUv1UUPWu4mC5
Co-authored-by: Claude <noreply@anthropic.com>
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ci/cddependenciesPull requests that update a dependency filedocumentationImprovements or additions to documentationsize/mskip-changesetPR has no user-facing published change; bypasses the changeset gatetooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@os-zhuang