Skip to content

feat(spec): declare namespace on TemplateManifestSchema as a scaffold-only extra (#6861) - #7317

Merged
os-zhuang merged 1 commit into
mainfrom
claude/issue-6861-manifest-namespace
Aug 10, 2026
Merged

feat(spec): declare namespace on TemplateManifestSchema as a scaffold-only extra (#6861)#7317
os-zhuang merged 1 commit into
mainfrom
claude/issue-6861-manifest-namespace

Conversation

@os-zhuang

@os-zhuangos-zhuang commented Aug 10, 2026

Copy link
Copy Markdown
Contributor

Closes#6861

Executes the triage direction on #6861: the ENFORCE leg of ADR-0049 — declare the key, do not remove it.

Premise (verified on fresh origin/main @ 3e8e669c0)

All three legs of the issue still hold. Measured with a throwaway probe against the real schema before any edit:

ClaimVerdict
TemplateManifestSchema does not declare namespaceconfirmed — shape keys are manifestId, displayName, description, visibility, category, tags, iconUrl, homepageUrl, license, publisher, isStarter, translations, name, specVersion, skills, preview, scaffold, readmePath
the blank template still ships "namespace": "blank"confirmed
create-objectstack rewrites it; rewrite-identity.ts reads it backconfirmed at src/index.ts:281 and src/rewrite-identity.ts:73

The probe also pinned the consequence, which the issue predicted but did not measure: the schema is a default strip-mode object, so parsing a manifest through it dropped the key and answered success —

3) full manifest with namespace parses? true
namespace survived? false =. {"manifestId":"com.acme.blank","displayName":"Blank Starter","name":"blank","specVersion":"^6.0.0"}
5) malformed namespace rejected pre-change? false

A malformed value was therefore accepted, not refused. That is the enforce-or-remove shape, and since the key is genuinely live, enforce is the right leg.

Corroboration found while verifying: content/docs/getting-started/your-first-project.mdx:87 has advertised objectstack.manifest.json # name, namespace, specVersion to users all along. The schema was the only surface denying the key existed — so no hand-written doc needed changing, only the generated reference.

Changes

  • packages/spec/src/cloud/template-manifest.zod.ts — declares namespace, optional, reusing CreatePackageRequestSchema.shape.namespace for its value constraints. The .omit() of the inherited create-request field stays, and the key is re-declared with its own describe; the comment that used to explain the omission now explains the split (omitted for publish, declared for scaffold). A TSDoc block on the field names both readers and states plainly that this is not the publish source.
  • packages/spec/src/cloud/package-namespace.test.ts — the block that pinned the omission is replaced by five pins for the new semantics; the §A.7 vocabulary table gains the template field, and blank joins the value table as the one in-repo instance of the scaffold surface.
  • packages/spec/authorable-surface/cloud.json — one key added, cloud/TemplateManifest:namespace.
  • content/docs/references/cloud/template-manifest.mdx — regenerated; adds the namespace row with the scaffold-only wording.
  • .changeset/template-manifest-scaffold-namespace.md@objectstack/spec minor.

Why declare rather than remove

The removal leg would drop the rewrite-identity.ts:73 fallback and make objectstack.config.ts the single source — but that fallback is what #4902 added for the remote-template shape, where the tree has no config to read. Triage ruled the key live and routed this as the enforce leg, and the measurement above agrees: the key is written, rewritten and consumed on a real shipped file.

Reusing the constraints without inheriting the field is the load-bearing detail. Value constraints come from the publish field, so both surfaces judge every namespace identically (ADR-0048 addendum §A.7, "two gates, one vocabulary") — otherwise the scaffolder could stamp a namespace publish would later refuse. The meaning stays split: publish reads its namespace off the compiled artifact's manifest.namespace (§A.2 Phase A1), never off this file. Collapsing the two into one inherited field is exactly the second-authoring-surface footgun #6760 avoided, and it stays avoided.

Reverse verification — direction predicted first

Predicted before running: with the schema change removed, the no-strip pin, the declares-the-field pin, the rejection pin and the describe pin go red; is OPTIONAL stays green, because a manifest with no namespace parses either way.

Measured (git checkout origin/main -- template-manifest.zod.ts, then the pin file):

× the publish payload judges every namespace exactly as manifest.namespace does
× every field is optional, so "absent" means the same thing on all sides
× declares the field, alongside the rest of the create-request projection
× SURVIVES the parse — the key is no longer silently stripped
× REJECTS a malformed namespace with the shared coded issue at the namespace path
× says scaffold-only in its describe, and is NOT the publish field
Test Files 1 failed (1)
Tests 6 failed | 8 passed (14)

One honest correction to the prediction.is OPTIONAL did stay green as predicted, but six reds appeared where four were expected. Two of them — every field is optional and the §A.7 table — go red by TypeError on undefined.safeParse, not by a verdict flip: both now touch TemplateManifestSchema.shape.namespace, which does not exist pre-change. Only four reds are genuine verdict flips (declares the field, SURVIVES the parse, REJECTS a malformed, says scaffold-only). Recording this because a red count is not by itself evidence — the reason each one went red is.

Gates

GateResult
pnpm --filter @objectstack/spec buildgreen
check:generated11/11 green. First run reported check:docs stale only; fixed by gen:docs. check:api-surface and check:export-origins were green throughout — no public export added or removed, so the dual-snapshot rule is not triggered.
authorable-surface.base.jsonuntouchedgen:authorable-surface-base deliberately not run; this is an addition, not a re-anchoring.
pnpm --filter @objectstack/spec testgreen — 359/359 files, 9385/9385 tests
pnpm --filter create-objectstack testgreen — 2/2 files, 28/28 tests
pnpm --filter @objectstack/spec typecheckgreen
pnpm --filter create-objectstack typecheckgreen
node scripts/check-nul-bytes.mjsgreen

One detour worth recording. In the first full spec run — taken before the artifact regen — scripts/build-schemas-check-mode.test.ts failed 23 of 60. I ruled out an environmental cause by running the same file on a clean origin/main worktree, where it passes 60/60; the real cause is that those sandboxed box tests read the in-tree authorable-surface/cloud.json, which my change had made stale. The regen fixed it, and the full run above — 359/359 files including that one — is the post-regen confirmation. Recording it because "build a schema, then regenerate" is load-bearing here in a way the failure did not advertise: the red pointed at a box-test file with nothing to do with namespace.

Special inspection items

  1. A restore mishap worth reviewing the diff for. During reverse verification I removed the fix with git checkout origin/main -- ... while the work was still uncommitted, so git checkout branch -- ... could not bring it back — the branch tip was origin/main. I re-applied the three edits from context and committed before doing anything else. The zero-diff gen:schema re-run is evidence the restore is faithful, but the source diff deserves a real read rather than a skim.
  2. .describe() sits on the ZodOptional wrapper, not the inner string. It renders correctly in the generated docs row (verified in the mdx diff), but it is the reason the describe pin reads .description off the field rather than off .unwrap().
  3. The blank template manifest still does not validate against this schema — it lacks the required manifestId. That is a separate pre-existing drift, untouched here and filed as The shipped blank template's objectstack.manifest.json does not validate against TemplateManifestSchema — it declares no manifestId #7319.

Out of scope

Filed as #7319 (finding, unassigned): the shipped blank template's objectstack.manifest.json does not validate against TemplateManifestSchema, because the schema inherits manifestId as required and the file declares none. A second declared-vs-live drift on the same file, unrelated to namespace, with its own fork to settle — so it is filed rather than fixed here.


Generated by Claude Code

…ld-only extra (#6861)
`objectstack.manifest.json` carries a live `namespace` key that the schema
describing that file did not declare: the blank template ships it,
`create-objectstack` rewrites it at scaffold time, and `readTemplateNamespace`
reads it back as the fallback source for the template's original namespace.
The schema is a default strip-mode object, so parsing the manifest through it
dropped the key and answered success, and a malformed value passed green.
ADR-0049 enforce leg: declare it, do not remove it. The field reuses
`CreatePackageRequestSchema.shape.namespace`'s constraints (one vocabulary,
ADR-0048 addendum §A.7) but is re-declared rather than inherited, carrying a
describe that says it is scaffold-only and NOT the publish namespace — publish
still reads that off the compiled artifact's `manifest.namespace` (§A.2), so
the second-authoring-surface footgun #6760 avoided stays avoided.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PiRUoQkTSBBmpyXBY3cVn2
@vercel

vercelBot commented Aug 10, 2026

Copy link
Copy Markdown

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

1 Skipped Deployment
ProjectDeploymentActionsUpdated (UTC)
objectstackIgnoredIgnoredAug 10, 2026 6:33am

Request Review

@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

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

106 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 @objectstack/spec)
  • content/docs/automation/connectors.mdx(via @objectstack/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 @objectstack/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/cli.mdx(via @objectstack/spec)
  • content/docs/deployment/tenancy-modes.mdx(via @objectstack/spec)
  • content/docs/deployment/troubleshooting.mdx(via @objectstack/spec)
  • content/docs/deployment/validating-metadata.mdx(via @objectstack/spec)
  • content/docs/getting-started/build-with-claude-code.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/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 @objectstack/spec)
  • content/docs/kernel/index.mdx(via packages/spec)
  • content/docs/kernel/runtime-services/data-service.mdx(via @objectstack/spec)
  • content/docs/kernel/runtime-services/email-service.mdx(via packages/spec)
  • content/docs/kernel/runtime-services/examples.mdx(via @objectstack/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 @objectstack/spec)
  • content/docs/kernel/runtime-services/sms-service.mdx(via packages/spec)
  • content/docs/kernel/runtime-services/storage-service.mdx(via @objectstack/spec)
  • content/docs/kernel/services-checklist.mdx(via @objectstack/spec)
  • content/docs/kernel/services.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/permissions/system-context.mdx(via packages/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/http-protocol.mdx(via @objectstack/spec)
  • content/docs/protocol/kernel/i18n-standard.mdx(via @objectstack/spec)
  • content/docs/protocol/kernel/index.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/knowledge.mdx(via @objectstack/spec)
  • content/docs/protocol/objectql/index.mdx(via @objectstack/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/ui/actions.mdx(via @objectstack/spec)
  • content/docs/ui/apps.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/field-grouping-and-order.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)

7 release-owned page(s) also reference the affected code. These are read-only:

  • 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/v16.mdx(via @objectstack/spec)
  • content/docs/releases/v17.mdx(via @objectstack/spec)
  • content/docs/releases/v9.mdx(via @objectstack/spec)

content/docs/releases/ is RELEASE-OWNED (AGENTS.md "Documentation Guardrails"): release
notes are written centrally at release time, and a code PR that edits them is the exact PR
that guardrail exists to stop. They are still audited — read-only. If one of them is actually
wrong, file an issue or open a dedicated docs-only PR; do not edit it here.

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.

@github-actionsgithub-actionsBot added documentation Improvements or additions to documentation tests tooling labels Aug 10, 2026
@os-zhuang
os-zhuang marked this pull request as ready for review August 10, 2026 06:54
@os-zhuang
os-zhuang added this pull request to the merge queueAug 10, 2026
Merged via the queue into main with commit a47f338Aug 10, 2026
28 checks passed
@os-zhuang
os-zhuang deleted the claude/issue-6861-manifest-namespace branch August 10, 2026 07:13
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentationImprovements or additions to documentationsize/mteststooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

objectstack.manifest.json carries a live namespace key that TemplateManifestSchema does not declare

2 participants

@os-zhuang@claude