Skip to content

fix(spec): published agent prompts reference real exports — Object no longer binds the JS global (#9545) - #9615

Merged
os-steve merged 1 commit into
mainfrom
claude/issue-9545-prompt-import-fabrications
Aug 18, 2026
Merged

fix(spec): published agent prompts reference real exports — Object no longer binds the JS global (#9545)#9615
os-steve merged 1 commit into
mainfrom
claude/issue-9545-prompt-import-fabrications

Conversation

@claude

@claudeclaudeBot commented Aug 18, 2026

Copy link
Copy Markdown
Contributor

Fixes#9545

Three published agent-authoring prompts in packages/spec/prompts/ told agents to import five symbols @objectstack/spec does not export. Four failed loudly. The fifth did not — and that is the one this card exists for.

Verified at 00ad76978 (the final commit; the gate union below was re-run on that tree).

Why the fifth one is different — demonstrated, not asserted

import { Object } from '@objectstack/spec/data' does not resolve, so the annotation in export const AccountObject: Object = { ... } binds to the JavaScript globalObject. I compiled that exact shape against the built package:

ProbeResult
import { type Object } from '@objectstack/spec/data'TS2305: Module '"@objectstack/spec/data"' has no exported member 'Object'
export const AccountObject: Object = { this_is_not_a_field: 'x', neither_is_this: 12345 }exit 0 — accepts arbitrary garbage

That second row is the defect: metadata authored from this prompt type-checks against a type that constrains nothing, and every signal says it passed.

Resolution proofs

Every substitution was measured against the built.d.ts (pnpm --filter @objectstack/spec build first), then compiled from packages/objectql, which is both a real consumer and the literal audience of implement-objectql.md.

FormValid inputBad input
ObjectSchema.create({ ... })exit 0TS2322 on a bogus field type
z.infer< typeof ObjectSchema >exit 0TS2353 on an unknown key
ObjectStackDefinitionSchema.parse(...) from @objectstack/specexit 0
RLSUserContextSchema + RowLevelSecurityPolicySchema from @objectstack/spec/securityexit 0

The corrected create-new-project.md snippet was extracted verbatim from the committed file and compiled: exit 0.

Per-symbol before / after, with referent justification

1. Objectcreate-new-project.md:52

-import { Object } from '@objectstack/spec/data';-export const AccountObject: Object = {+import { ObjectSchema } from '@objectstack/spec/data';+export const AccountObject = ObjectSchema.create({

Referent: not a name-swap — ObjectSchema is a Zod const, unusable as an annotation. The house authoring convention, measured rather than chosen: ObjectSchema.create({ ... }) appears 34 times across the example apps, and the annotation form appears zero times.

⚠️ Two things this fix must not break, both measured:

  • create-new-project.md:87-89 legitimately calls Object.values(objects) — the JS global. Importing anything named Object would shadow it. That alone rules out introducing a local Object alias.
  • Making the form genuinely constrain exposed that the prompt's own example was already invalid: enable.audit and enable.workflow do not exist. The fabricated annotation had been hiding it. They are now trackHistory / files — the exact pair the enable docstring in data/object.zod.ts uses, with trackHistory documented as the audit-trail flag. Declared here because it is a same-class fix beyond a pure substitution.

2. type Objectimplement-objectql.md:17

-import { type Object, ObjectSchema } from '@objectstack/spec/data';+import { z } from 'zod';+import { ObjectSchema, type Field, QuerySchema } from '@objectstack/spec/data';+type ObjectMetadata = z.infer< typeof ObjectSchema >;+type Query = z.infer< typeof QuerySchema >;

Only the fabricated type Object half was dropped. Referent:Field and QuerySchema are real exports on ./data, kept as-is. No bare Object type is exported — and the omission is deliberate, since every sibling has the full triple (Field / FieldParsed / FieldSchema) while Object has only ObjectSchema. Deriving it matches prompts/instructions.md ("interfaces must be inferred from Zod, z.infer< typeof X >") and spec's own src/contracts/schema-driver.ts, which writes type DataObject = z.infer< typeof ObjectSchema > for exactly this reason.

3. ManifestSchemaimplement-objectos.md:16

-import { ManifestSchema } from '@objectstack/spec/system';+import { ObjectStackDefinitionSchema } from '@objectstack/spec';

Referent: the card framed this as AppManifestSchema vs DeployManifestSchema; both are wrong. The prompt names its own subject — objectstack.config.ts — whose authoring surface is defineStack / ObjectStackDefinitionSchema, on the root entry, not /system. AppManifestSchema installs an app into a running stack; DeployManifestSchema describes a deploy bundle. The subpath changes with the symbol.

4. IdentitySchema + PolicySchemaimplement-objectos.md:25

-import { IdentitySchema, PolicySchema } from '@objectstack/spec/system';+import {+ RLSUserContextSchema,+ RowLevelSecurityPolicySchema,+} from '@objectstack/spec/security';

Referent:/system returns zero matches for Identity — there is no IdentitySchema and no bare Identity either, so the card's own "measured reality" column was itself a fabrication. Substituting it as the card instructed would have written a new fabricated symbol into a published prompt.

Rule #2's intent is "all request handlers validate identity; no operation proceeds without checking policy". The honest referents live on ./security:

  • RLSUserContextSchema — documented in security/rls.zod.ts as "the current user's context for RLS evaluation", i.e. the per-request security context.
  • RowLevelSecurityPolicySchema — the same file calls per-policy RLS "the live, enforced surface".

A sentence was added stating plainly that no bare Identity/Policy schema exists and that broader posture lives in the qualified schemas, so the next reader does not re-derive it.

5. Three dead source paths — implement-objectos.md "Key Files to Watch"

-- `system/manifest.zod.ts`: The "Kernel Configuration".-- `system/identity.zod.ts`: The "Security Context".-- `system/events.zod.ts`: The "System Bus".+- `stack.zod.ts`: The "Kernel Configuration" (`ObjectStackDefinitionSchema`, `defineStack`).+- `security/rls.zod.ts`: The "Security Context" (`RLSUserContextSchema`, `RowLevelSecurityPolicySchema`).+- `kernel/events.zod.ts`: The "System Bus" (`EventSchema`; ...).

Each now serves the same watch-purpose with a file that exists (all nine paths across both prompts re-verified present). kernel/events.zod.ts is chosen over kernel/events/core.zod.ts where EventSchema is physically declared, because that barrel documents itself as the stable entrypoint and the events/* sub-modules as internal — the prompt now says so.

Baseline reconciliation

The five prompt entries are deleted in this same PR (16 → 11). The diff is deletion-only — no reformatting.

✓ check:published-readme-exports — 60 published document(s) across 77 workspace package(s);
167 import statement(s), 47 workspace type entr(ies).
11 known instance(s) still in the baseline; 2 of the findings are call sites.

⚠️ The gate needs the whole workspace built, not just spec — an unbuilt package is a hard error by design. Full pnpm build (71/71) precedes the run.

Reverse verification

Re-added one fixed entry to the baseline. Predicted red on a stale entry (the baseline reconciles in both directions); observed exactly that, naming the entry:

✗ check:published-readme-exports — 1 stale baseline entr(ies)
@objectstack/spec|packages/spec/prompts/create-new-project.md|import|@objectstack/spec/data|Object
Good news, and it is still a failure: these README claims now resolve, so their
entries ... are dead text.

Removed; green again, diff back to deletion-only.

Gates

Derived from the actual changed paths via node scripts/pm/dispatch-gates.mjs, all green at 00ad76978:

check:published-readme-exports · check:nul-bytes (+ manual control-char scan) · spec check:liveness · spec check:strictness-ledger · spec check:empty-state · spec check:variant-docs · check:merge-driver · check:type-source-resolution · check-dev-prereqs · docs-audit/check-affected-docs

Plus @objectstack/spectest 409 files / 10937 tests passed, and typecheck OK.

Out of scope

Rule #3 names RequestEnvelope and ResponseEnvelope in prose; neither exists as an export (only ResponseEnvelopeConfig*). Prose symbols are the gate's structural blind spot and were explicitly kept in #9532's lane by the PM ruling, so they are filed separately rather than folded in here. #9544 is a separate card and its README files are untouched.


Generated by Claude Code

Three published agent-authoring prompts in packages/spec/prompts told agents to
import five symbols @objectstack/spec does not export. Four failed loudly. The
fifth did not: `import { Object } from '@objectstack/spec/data'` does not
resolve, so `export const AccountObject: Object = { ... }` bound to the
JavaScript global instead, and metadata authored from that prompt type-checked
against a type that constrains nothing -- false assurance handed to an
automated author.
Every substitution was measured against the built .d.ts, not guessed:
- Object -> ObjectSchema.create({ ... }), the house authoring convention (34
uses across the example apps; zero uses of the annotation form). Making the
form genuinely constrain exposed that the prompt's own example set
enable.audit / enable.workflow, neither of which exists; they are now
trackHistory / files, the pair the schema's own docstring uses.
- implement-objectql.md keeps the real Field and QuerySchema imports and
derives the metadata type as z.infer of typeof ObjectSchema, matching
prompts/instructions.md and spec's own src/contracts/schema-driver.ts. No
bare Object type is exported -- it would shadow the JS global.
- ManifestSchema -> ObjectStackDefinitionSchema on the package root. The
prompt's subject is objectstack.config.ts, which is neither /system manifest.
- IdentitySchema / PolicySchema have no bare referent anywhere in spec. Rule #2
now names RLSUserContextSchema and RowLevelSecurityPolicySchema from
@objectstack/spec/security.
- The three dead "Key Files to Watch" paths now point at stack.zod.ts,
security/rls.zod.ts and kernel/events.zod.ts.
The five baseline entries are deleted in the same commit; the gate is
reconciled in both directions and green.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Fs18A2DdXLVN2h8PaaFBcP
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

Nothing in this diff resolved to a documentable surface (no symbol, route or SDK anchor derived from 1 changed package(s)), so this run has no opinion about the docs.

What this run could not see
  • 3 changed file(s) yielded no anchor (packages/spec/prompts/create-new-project.md, packages/spec/prompts/implement-objectos.md, packages/spec/prompts/implement-objectql.md) — pages documenting those are invisible to this run

Coarse fallback — 113 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): node scripts/docs-audit/affected-docs.mjs --json b057e53f46e47daa337608d4c6d249a76400b91fpackageMentionDocs.

@github-actionsgithub-actionsBot added documentation Improvements or additions to documentation tooling labels Aug 18, 2026
@os-steve
os-steve marked this pull request as ready for review August 18, 2026 13:51
@os-steve
os-steve added this pull request to the merge queueAug 18, 2026
Merged via the queue into main with commit 890b38fAug 18, 2026
26 checks passed
@os-steve
os-steve deleted the claude/issue-9545-prompt-import-fabrications branch August 18, 2026 14:26
os-steve added a commit that referenced this pull request Aug 18, 2026
…tract, not a seed count (#9649)
The block claimed the file was "seeded from the five instances #9532
measured". It shipped with 16 entries (PR #9546) and has since shrunk to
0 through #9602, #9615 and #9581, so the sentence was wrong on day one
and the gap kept changing meaning as the ledger shrank.
Rewritten to state the contract rather than a number: what an entry is
(one known instance still awaiting repair, debt not exemption), that the
count is read from `entries` and never asserted in prose, that
`entries: []` is the success state rather than a corrupt or deletable
file, and that absence from the file means measured-and-clean rather
than unscanned -- which is what the plugin-audit negative control now
says for every package.
No baseline entry is added, removed or edited.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XqDQYVU5smx29ts9pAErja
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentationImprovements or additions to documentationsize/mtooling

Projects

None yet

2 participants

@os-steve@claude