Repository navigation
feat(spec): export the anonymous-form-intake candidates from @objectstack/spec/ui; metadata-core re-exports the same bindings - #22056
Conversation
…ule from @objectstack/spec/ui publicFormSlug, anonymousFormIntakeSlug, anonymousFormIntakeCandidates, anonymousFormIntakeSlugs and the AnonymousFormIntakeCandidate type move, unchanged in what they decide, from @objectstack/metadata-core into @objectstack/spec/ui beside SharingConfigSchema. metadata-core re-exports the same bindings, so the server callers keep importing them from there and one copy remains; the identity is pinned in its test. Claude-Session: https://claude.ai/code/session_01GV6oYwgc1kWiUCb1YaprQ7 Co-authored-by: Claude <noreply@anthropic.com>
…eference for the new /ui exports Claude-Session: https://claude.ai/code/session_01GV6oYwgc1kWiUCb1YaprQ7 Co-authored-by: Claude <noreply@anthropic.com>
… patch for the /ui anonymous-form-intake export Claude-Session: https://claude.ai/code/session_01GV6oYwgc1kWiUCb1YaprQ7 Co-authored-by: Claude <noreply@anthropic.com>
📓 Docs Drift Check5 anchor(s) derived from 2 changed package(s); no hand-written page names any of them. What this run could not see
Coarse fallback — 139 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): Which tree this was computed onThis run read A worktree cut from an older # while this PR is open — GitHub drops the merge commit once it closes
git fetch origin 292e65b61043ccd97f891df881f11f68204dec2d && git checkout 292e65b61043ccd97f891df881f11f68204dec2d
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 5bd8cb100b9ab7043d1c3bb6db31c99b5ce630b9 6c5741c6b71966e88773503fd0bfb41fe5b0f38d && git checkout -B drift-repro 5bd8cb100b9ab7043d1c3bb6db31c99b5ce630b9 && git merge --no-ff 6c5741c6b71966e88773503fd0bfb41fe5b0f38d
node scripts/docs-audit/affected-docs.mjs --json 5bd8cb100b9ab7043d1c3bb6db31c99b5ce630b9 |
Contract reviewServed-tier: Inputs read: card #22047 (body, claim ① Derived judgmentsPublic surface, Public surface, The rule's accept set (unchanged — right; the card's binding guard). Spec pins (right). Identity pin (right, and able to fail). Prime Directive 2 (allowed). PD2 reads "No business logic in Published prose, sentence by sentence.
② Semver level
③ Boundary flagsDev report (
Flags raised by this review:
Implemented-by: VERDICT: PASS Generated by Claude Code |
…s no server state; precedent and ruling attributed correctly The changeset's last bullet and the module's 'What stays' paragraph no longer group anonymousFormObjectName with the halves that read server state, and name only the two checks that can withhold an open form. The 'Why this half lives in packages/spec' paragraph cites expandViewContainer as the placement precedent only, states Prime Directive 2 as ADR-0053 D-D2 reads it, and attributes the byte-for-byte reason to the triage ruling. Comment and changeset text only. Claude-Session: https://claude.ai/code/session_01GV6oYwgc1kWiUCb1YaprQ7 Co-authored-by: Claude <noreply@anthropic.com>
Fixes #22047
Clause-②: yes (widening: new exports on the published
@objectstack/spec/uientry;@objectstack/specchangeset at leastminor)What changes
@objectstack/spec/ui:publicFormSlug,anonymousFormIntakeSlug,anonymousFormIntakeCandidates,anonymousFormIntakeSlugsand theAnonymousFormIntakeCandidatetype. This is the candidates half of the anonymous-form-intake rule, which the triage ruling on console(public forms page): the developer Public Forms page lists a form as published by its own reading of sharing, which ignores sharing.enabled — a form the server no longer serves still shows as published objectui#11545 (5967405932) asks a console to read instead of re-deriving "published" from the sharing keys.packages/metadata-core/src/anonymous-form-intake.ts, unchanged except for indentation (2 spaces, as in the rest ofpackages/spec).diff -wbetween the BASE lines 52-105 and the new module is empty. The scan still covers the three shapes in the same order: nestedform, thenformViewsentries, then aviewKind: 'form'item'sconfig.@objectstack/metadata-corere-exports the same bindings (export { … } from '@objectstack/spec/ui', plusexport typefor the interface), so one copy remains. Its remaining code importspublicFormSlugand the type from the spec. Its posture, withdrawal-layer and object-name parts stay where they were.@objectstack/restand@objectstack/metadata-protocolare not edited and keep importing frommetadata-core.packages/metadata-core/src/index.tsis unchanged..changeset/22047-spec-ui-anonymous-form-intake.md:@objectstack/specminor,@objectstack/metadata-corepatch(its builtdist/index.{js,cjs,d.ts,d.cts}now import these functions from@objectstack/spec/uiinstead of defining them).Where it lives, and why (H3)
It goes in a new module,
packages/spec/src/ui/anonymous-form-intake.ts, next tosharing.zod.ts. It is not added tosharing.zod.ts, for three reasons:sharing.zod.tsimportszodand two schema helpers.spec/uithat do not live inside a schema module.chart-aggregate.ts,i18n-label-resolver.tsandview-grouping-query.tsare sibling non-.zod.tsmodules.expandViewContainersits inview.zod.tsonly because it reads that module's member constants. These four functions read no schema.metadata-core, so the move is easy to follow in history.Trade-off:
files[]shipssrc/**/*.zod.tsas source, so this module's source is not in the tarball. Its JS and declarations are, indist/ui/index.{mjs,js,d.mts,d.ts}.Prime Directive 2 (no business logic in
packages/spec: schemas, types and constants only) holds here the way ADR-0053 D-D2 reads it. A pure helper that states what the contract's own vocabulary denotes is protocol, not business logic. It lives beside that vocabulary, and a server package re-exports it: D-D2 movednextUtcCalendarDayinto@objectstack/spec/dataand has@objectstack/corere-export it. These four functions only say whichsharingdeclarations open a form. The two checks that read server state (another layer's withdrawal, the tenancy posture) stay inmetadata-core. So doesanonymousFormObjectName, a pure read of the form and the view, because that is where this export's surface was drawn.expandViewContainer(view.zod.ts) is the placement precedent: a pure helper beside the schema it serves. The reason two codebases must agree on this rule byte for byte is the triage ruling on objectui#11545 (5967405932), as the module's header now says (patch round 1,6c5741c6).Pins and measurements
Identity pin (3). This is in
packages/metadata-core/src/anonymous-form-intake.test.ts; the existing cases are unchanged, and the pin adds 3 import lines and 1describe. For each of the four names it asserts that./anonymous-form-intake.js[name]and./index.js[name]aretoBe(Object.is)@objectstack/spec/ui[name].scripts/ablation-replace.mjswith the fix committed first. The re-export ofanonymousFormIntakeCandidateswas replaced by a wrapper that returns the spec function's answer.Tests 1 failed | 45 passed (46). Only the identity case foranonymousFormIntakeCandidatesfailed (expected [Function] to be [Function] // Object.is equality). Every behaviour test passed against the wrapper, so only the identity pin can catch a copy.c08671875) andgit diff HEADis empty. metadata-core's test reads its ownsrcdirectly, so the mutation needed no rebuild to take effect.H5: identity in the built dual output. A one-time node probe, run from
packages/rest, compared the four functions in metadata-core's built output with@objectstack/spec/ui's:dist/index.jsvsdist/ui/index.mjs)dist/index.cjsvsdist/ui/index.js)Parity pin (1).
packages/spec/src/ui/anonymous-form-intake.test.ts(26 cases) pins each of the three shapes on its own (open; withdrawn by either switch; no link), all three in one body in scan order, aconfigwithoutviewKind: 'form', slug normalisation, and raw input againstSharingConfigSchema.parseinput. Its expectations are the same as metadata-core's existing ones. A one-time parity probe compared the BASE metadata-core functions (git show 8caa131e5, lines 52-105) with the built spec/ui and metadata-core functions now. It compared candidate keys, key presence, slugs, whether each candidate is a form object from the input, and the slug set:formformViewsentry (plus an open sibling)viewKind: 'form'+configconfigwithoutviewKind: 'form'inquiry.view.ts, crmlead.view.ts, as containers and asexpandViewContaineritemsThat is 78 bodies with 0 mismatches, and 21 leaf inputs (
anonymousFormIntakeSlug,publicFormSlug) with 0 mismatches.H1. Lines 52-105 call nothing from
@objectstack/spec/security,applyInjectedSystemColumnsorresolveRecordWallOrganizationField. The new module has no imports, and the four bodies compile unchanged inpackages/spec.H2.
./uiis inbrowser-reachable-entries.json'sunjudgedlist, socheck:browser-reachable-entriesasserts nothing about it (it passed). The module is plainfunctiondeclarations with no top-level statements, and the package declares"sideEffects": false.Declaration surface downstream.
metadata-core'sdist/index.d.tsand.d.ctsnow reference@objectstack/spec/ui. Measured withtsc --noEmit --extendedDiagnostics --listFiles, building metadata-core from BASE source and then from HEAD source. These are absolute numbers from a shared box:packages/restpackages/objectqlpackages/plugins/plugin-securitypackages/metadata-protocolpackages/qa/http-conformanceThe one new file in
restisspec/dist/ui/index.d.ts, the CJS barrel, reached throughmetadata-core/dist/index.d.cts. The chunks it re-exports were already in that program in both flavours. tsc exited 0 in every program on both builds.Prose that named the old home (H4)
packages/spec/src/ui/sharing.zod.ts:19-23said the rule lives inanonymousFormIntakeCandidates"in@objectstack/metadata-core". It now namesanonymous-form-intake.tsbeside that module, which metadata-core re-exports to the server's doors.content/docs/references/ui/sharing.mdx:22-26was regenerated from that docblock bygen:docs, not edited by hand.packages/metadata-core/src/anonymous-form-intake.ts:13-20: the module docblock says the candidates half is declared in@objectstack/spec/ui, whose docblock is now the authority on it. The scan-shape paragraph moved with the code.packages/rest/src/rest-server.ts:10698reads "(anonymousFormIntakeCandidates,@objectstack/metadata-core)". That is where rest imports the function from, and metadata-core still exports it. The file is also outside this card's surface.packages/metadata-core/src/index.ts:141-145("both read this one rule").docs/qa/platform-checklistmechanism references nameanonymousFormIntakeWithdrawnInandanonymousFormExplicitWithdrawals, which stay in metadata-core.Verification (HEAD
cfdc8804f0; the source tree is the same as3e9a9e48b1plus the changeset)pnpm --filter @objectstack/spec build(JS + DTS): exit 0.check-dts-emittedreported 38/38.pnpm --filter @objectstack/spec check:generated: all 15 artifacts up to date. Before regeneration, 3 were stale:api-surface/(+5 rows inui.json),export-origins/(+5) andcontent/docs/references/**(the H4 sentence). They were regenerated withgen:api-surface,gen:export-originsandgen:docs.check:api-surface,check:export-origins,check:docs,check:exported-any,check:dual-source-exports(0 accepted dual-source),check:entry-nameability,check:browser-reachable-entries,check:liveness,check:llms-txt,check:skill-examples: exit 0.pnpm --filter @objectstack/spec test: 620 files, 18511 passed, 1 todo. The new file alone: 26 passed.pnpm --filter @objectstack/metadata-core test: 18 files, 415 passed.src/anonymous-form-intake.test.tsalone: 46 passed.pnpm --filter @objectstack/metadata-protocol test: 219 files passed, 3 skipped; 28135 tests passed. The focused run ofruntime-authoring-gate.public-form-intake,protocol.runtime-authoring-gateandprotocol.org-scoped-write-refusedpassed 257.pnpm --filter @objectstack/rest test: 260 files, 4914 passed, 326 skipped. The focused run ofpublic-form-routes,public-form-routes.stored-row,public-form-withdrawalandpublic-form-intake-availabilitypassed 76.pnpm --filter @objectstack/spec typecheckandpnpm --filter @objectstack/metadata-core typecheck: exit 0.--listFilesshows both new test files are in their packages' test programs.node scripts/pm/dispatch-gates.mjs --commandsatcfdc8804f0, and checked with--ran: 110 derived, 109 run, 0 unrun, 1 NOT MEASURED. The NOT MEASURED one ispnpm check:dual-build-cjs-loads, exit 3, PREREQUISITE NOT MET: it needs every package built. The CJS half of the H5 probe above loadedmetadata-core/dist/index.cjswithrequire. The 5 roster gates the lead list flagged under a touched directory also pass (check:meta-url-spellingandcheck:spec-changesthroughcheck:generated;check:authz-resolver,check:error-code-casing,check:filter-alias-parity).eslint --no-inline-config --format jsonover the 6 changed TS files reported 6 files, 0 errors and 0 warnings. All 6 are in the population ofeslint.config.mjs(files: ['**/*.{ts,…}']minusNEVER_LINTED, and--print-configresolves each one). That config never turns on type-aware linting (noparserOptions.project, no typed rules; see its own note near line 327), so this diff cannot change the result for any file it does not touch. The repo-widepnpm lintis left to CI.Acceptance notes
maingained feat(spec,analytics): a dataset answer's measure column states its aggregate, labelled or not (fields[].aggregate) #22021 (a spec analytics change) after this branch was cut. It touches none of these files and none of the generated artifacts, somainis not merged here, and the merge queue rebuilds on the currentmain.Generated by Claude Code