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):

pointers broken
main @ ce468c832 113 6
this PR 126 0

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:

case result
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-code exit 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'

not packages/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-zhuang os-zhuang added ci/cd tooling skip-changeset PR has no user-facing published change; bypasses the changeset gate labels Jul 17, 2026
@vercel

vercel Bot commented Jul 17, 2026

Copy link
Copy Markdown

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

Project Deployment Actions Updated (UTC)
spec Ready Ready Preview, Comment Jul 17, 2026 2:12pm

Request Review

@github-actions github-actions Bot 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
Contributor Author

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 main Jul 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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ci/cd dependencies Pull requests that update a dependency file documentation Improvements or additions to documentation size/m skip-changeset PR has no user-facing published change; bypasses the changeset gate tooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant