Skip to content

fix(scripts): parse a json doc fence strictly with parseJsonFence (objectui#10943) - #10985

Merged
objectstack-fleet[bot] merged 3 commits into
mainfrom
claude/issue-10943-strict-json-fence-census
Sep 28, 2026
Merged

objectstack-fleet[bot] merged 3 commits into
mainfrom
claude/issue-10943-strict-json-fence-census

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Fixes #10943

Clause-②: no — a docs-census strength change on repository tooling; no published contract, accept set or public surface changes (a contract review is still owed for the README fence retag)

What changes

This executes ruling A on objectui#10943: the maintainer's 「同意」 on 2026-09-28, recorded in the card's ruling comment 5869344478. The existing carriage census scripts/check-doc-expression-carriage.mjs now holds json and jsonc fences to different contracts:

  • json: parsed by parseJsonFence, imported from scripts/check-skill-examples.mjs and not copied. That function is JSON.parse and nothing else. No tolerance, no object-body retry and no multi-document split applies.
  • jsonc: unchanged. It keeps the four tolerances (comments, raw newlines in strings, trailing commas, elisions) and the object-body retry. The fences the blind-spot measurement reads (the languages the census does not judge) stay on that same tolerant path.
  • Unparsed entries: each one now carries the file, the fence's opening line, its language and the parse error. The CLI prints the two kinds apart. For json it prints the ruled remedy, 「retag as jsonc if the example needs comments or trailing commas」. For jsonc it keeps the old sanitizeFence remedy (H3). The CLI and the pin both print this text from UNPARSED_PRESCRIPTIONS, so the two cannot drift apart.
  • Posture: the CLI still exits 0 on anything it reads in a page. What goes red is the EXISTING pin, 'has no blind spot on the corpus it ships against'. It still asserts census.unparsed is empty, and a non-JSON json fence now lands on that list. This adds no new script, workflow, test file or gate. The ruling's non-generalisation also holds: the jsonc tolerance list is not widened, and no other fence language gains a parse check.
  • Header docblock (H4): the parse-surface section no longer says the tolerances apply to json. A new section records the ruling, the posture above and the import mechanics. The exit-code line also names a missing contract as an instrument failure.

Why the import is a guarded dynamic import(), not a static one

This was measured, not assumed. check-skill-examples.mjs imports typescript and check-doc-snippet-types.mjs at load. With a static import, the orphan pin ('is LOUD when the instrument itself is broken') runs the gate with no install. The gate then dies at module link with ERR_MODULE_NOT_FOUND for typescript, and the probe counted 0 occurrences of the asserted 'A failure, not a skip' line.

So the census loads the module with one const declaration holding an import() whose rejection settles into an error field. A top-level try is refused by check:entry-guard as a statement that runs on import. requireJsonContract() raises the held error inside the CLI's existing instrument check. A one-off probe (renderer and installed spec present, check-skill-examples.mjs absent) exits 1 with that line.

Cost: the census now loads typescript at start. Measured start times were about 0.2 s for the census --self-test and about 0.45 to 0.53 s to import check-skill-examples.mjs.

The surface under strict json, measured before any change (H2)

  • Population: the census's own scanFences over its whole surface. That surface is imported from check:doc-types and printed as content/docs, apps/*/docs, packages/*/README.md and README.md, so it is not content/docs alone.
  • Verdict: parseJsonFence(body, 'json').
  • Controls in the same run: a valid body was accepted, a trailing comma was rejected, and a comment was rejected.
  • Result: exactly one json fence failed. It is the VS Code settings example under 「⚙️ Configuration」 in packages/vscode-extension/README.md, which uses // comments because VS Code settings files are JSONC. The README's other json fence (the 「Example Schema」 one) already parses strictly. No fence was simply broken, so there is no docs bug to report.
  • Re-derive: node scripts/check-doc-expression-carriage.mjs on this branch prints the per-language row and names any unparsed fence.

The retag in packages/vscode-extension/README.md (for the contract review)

This is a published-docs prose change, and it is the smallest one available. One info string changes from json to jsonc on the Configuration fence, and the body bytes are unchanged. git diff over the file shows 1 line out and 1 line in. The site page content/docs/utilities/vscode-extension.mdx already tags its settings example jsonc, so the README now agrees with it. The other gates that read that fence's tag are green: check:doc-fences and check:doc-types (see Evidence).

CONTRIBUTING.md: one sentence this change made false

Its json-fence convention paragraph said 「Nothing enforces it for content/docs today」 and called whether a gate should block such a fence 「the open decision objectui#10943」. After this change, both statements are wrong. The sentence now names the census, the pin and the fact that the tolerances apply to jsonc only. ⚠️ This file is outside the claim's declared file surface. The edit was made because a statement this change makes false is owed in the same change; the report names it as a deviation.

Evidence

Readings are at head 33012bd4d6 unless a line says otherwise. Base is deca847a8a.

  • Census before/after
    • Base: json 221 (221 parsed, 0 unparsed); jsonc 15 (15 parsed, 0 unparsed).
    • Head: json 220 (220 parsed, 0 unparsed); jsonc 16 (16 parsed, 0 unparsed).
    • A diff of the two full census outputs differs in that one line only, so the judged findings (nodes, ${…} sites, carried count, blind-spot lists) did not move. The --list inventories differ only in the retagged fence's language.
    • --self-test exits 0 and prints Controls pass.
  • Ablation: one planted bad json fence on the real surface, with the //-comment class, planted in content/docs/guide/expressions.md through objectstack's scripts/ablation-replace.mjs in wrap mode with its EXIT/INT/TERM restore.
    • Head leg (11fc05b98f): the pin went RED, Tests 1 failed | 58 skipped (59). The received entry was "lang": "json" with "reason": "invalid-json: Expected property name or '}' in JSON at position 4 (line 2 column 3)". The failure message began: A json fence here is NOT JSON — a docs defect, not a blind spot: retag as jsonc if the example needs comments or trailing commas; …
    • Base leg (deca847a8a, a separate throwaway checkout): the identical plant left the same pin GREEN, Tests 1 passed | 46 skipped (47). The old tolerant parse read the comment away. This is the strength increase itself.
    • Restore, both legs: the tool reported blob == HEAD blob and git diff HEAD empty. Independently, git diff --quiet HEAD exited 0 and the plant marker count went back to 0.
    • Void attempts: the first head attempt was a no-op. The tool refused it because the replacement still contained the anchor (anchor count 1 to 1), and vitest never ran, so it was re-anchored. The first base attempt hit a vitest startup error (a symlinked node_modules lacked @vitejs/plugin-react), which is not a verdict. It was re-run after a real pnpm install in that checkout.
  • Tests: pnpm exec vitest run --maxWorkers=2 over 15 files passed, Test Files 15 passed (15), Tests 911 passed (911). The files are the census test, check-skill-examples.test.ts, markdown-fence-scan.test.ts (it imports scanFences), entry-guard-wiring.test.ts, and every test recorded by scripts/markdown-test-inputs.mjs --list as reading the README or CONTRIBUTING.md. The census test itself went from 47 to 59 tests.
  • Gates, each exit 0 and each re-run after the last code commit:
    • pnpm type-check:scripts. --listFiles shows it compiles the edited test.
    • pnpm lint:root: 0 errors. All 34 warnings are in files this PR does not touch.
    • pnpm check:doc-types
    • node scripts/check-doc-fence-languages.mjs --self-test and node scripts/check-doc-fence-languages.mjs
    • pnpm check:control-bytes
    • pnpm check:new-line-citations, with 0 new citations
    • pnpm check:entry-guard
    • pnpm check:pre-install-import-graph
    • pnpm check:test-path-roots
    • pnpm check:installed-pin-claims
    • node scripts/check-doc-links.mjs
    • node scripts/check-governed-queue-guard.mjs --test on the 4 paths, which prints NOT GOVERNED
    • node scripts/check-changeset-presence.mjs, which says no changeset is owed: the four changed files are not published source of any released package
  • NOT MEASURED
    • pnpm check:readme-exports: prerequisite not met, because every package's dist/index.d.ts is absent without a full build. This diff changes no import line in any README.
    • pnpm check:skill-examples: not run, because its module graph is unchanged. The census imports it, and nothing it imports changed. Its test file is in the 15 above.

Acceptance notes

These are recorded here and not filed.

  • The census row in content/docs/guide/ci-cd-pipeline.md describes the walk as content/docs/**, every apps/*/docs/** tree and the root README.md. It omits the package-README leg, which predates this PR. This change does not make that row false: the CLI it describes still exits 0 regardless. Carrier: none.
  • The CONTRIBUTING.md convention says a raw newline inside a string is invalid under both tags. The census's jsonc tolerance 2 re-escapes raw newlines, so a jsonc fence stays more tolerant here than the written convention. The ruling keeps that list as is. Observation only.

Related: objectui#10088 · PR objectui#10942 (the repairs this lands after) · objectui#7474 (the skills-tree precedent)


Generated by Claude Code

…eJsonFence

objectui#10943, ruled A. `check-doc-expression-carriage.mjs` now judges a
`json` fence with `parseJsonFence`, imported from `check-skill-examples.mjs`:
`JSON.parse` and nothing else. `jsonc` keeps the four tolerances and the
object-body retry, unchanged. Each entry on the unparsed list carries the
file, the fence's opening line, its language and the parse error. For `json`
the census prints the ruled remedy, 「retag as `jsonc` if the example needs
comments or trailing commas」.

The existing pin 'has no blind spot on the corpus it ships against' still
asserts the list is empty. It now reds a pull request that adds a non-JSON
`json` fence. No new script, workflow, test file or gate.

The import is a guarded dynamic import(), because that module loads
`typescript`. A static import would turn the orphan pin's loud instrument
failure into a module-resolution stack trace.

The VS Code settings fence in packages/vscode-extension/README.md needs its
`//` comments, so it is retagged `jsonc`. CONTRIBUTING.md stops saying that
nothing enforces the convention on the docs surface.

Claude-Session: https://claude.ai/code/session_01EBx9rvB7dufCz4at53x35U
Co-authored-by: Claude <noreply@anthropic.com>
`check:entry-guard` refuses a top-level `try` in a file that exports: it is a
statement that runs on import. The guarded `import()` of
`check-skill-examples.mjs` is now one `const` declaration. A rejected import
settles into `error` rather than throwing, and `requireJsonContract` still
raises it inside the CLI's instrument check with the "A failure, not a skip"
line.

Claude-Session: https://claude.ai/code/session_01EBx9rvB7dufCz4at53x35U
Co-authored-by: Claude <noreply@anthropic.com>
@github-actions github-actions Bot added documentation Improvements or additions to documentation tests labels Sep 28, 2026
@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

Metric Value Budget
Eager closure (gzip, 330 chunks) 3103.9 KB 3104.5 KB
Main entry chunk (gzip) 149.7 KB 350 KB
Entry file index-q9OI0-wf.js —
Status PASS —

The eager closure is every chunk the entry reaches through static imports — what the browser fetches and parses before the app renders. The entry chunk on its own is a small fraction of it.


📦 Bundle Size Report

Package Size Gzipped
app-shell (consoleActionDispatch.js) 0.20KB 0.19KB
app-shell (index.js) 16.58KB 6.17KB
app-shell (runtime-config.js) 20.68KB 7.36KB
app-shell (types.js) 0.01KB 0.04KB
app-shell (urlParams.js) 10.06KB 3.86KB
auth (ActiveOrganizationStorage.js) 27.95KB 10.04KB
auth (AuthContext.js) 0.31KB 0.24KB
auth (AuthGuard.js) 2.07KB 1.00KB
auth (AuthProvider.js) 40.22KB 10.61KB
auth (AuthShell.js) 3.49KB 1.40KB
auth (ForgotPasswordForm.js) 12.21KB 3.45KB
auth (LoginForm.js) 18.17KB 5.40KB
auth (PreviewBanner.js) 0.90KB 0.50KB
auth (RegisterForm.js) 6.72KB 2.24KB
auth (SocialSignInButtons.js) 9.70KB 3.93KB
auth (UserMenu.js) 3.39KB 1.21KB
auth (auth-gate-events.js) 1.29KB 0.66KB
auth (authStyles.js) 5.04KB 1.72KB
auth (createAuthClient.js) 40.70KB 10.94KB
auth (createAuthenticatedFetch.js) 8.52KB 3.45KB
auth (index.js) 3.63KB 1.64KB
auth (invitation-status.js) 1.22KB 0.70KB
auth (org-roles.js) 6.66KB 2.78KB
auth (phone-identifier.js) 1.11KB 0.66KB
auth (types.js) 0.59KB 0.35KB
auth (useAuth.js) 5.30KB 1.02KB
auth (useWorkspaceAdminStatus.js) 11.08KB 4.58KB
collaboration (CommentThread.js) 27.13KB 7.95KB
collaboration (LiveCursors.js) 3.17KB 1.27KB
collaboration (PresenceAvatars.js) 6.49KB 2.64KB
collaboration (PresenceProvider.js) 2.79KB 1.13KB
collaboration (index.js) 1.68KB 0.73KB
collaboration (useCollaborationTranslation.js) 6.05KB 2.52KB
collaboration (useCommentSearch.js) 1.98KB 0.88KB
collaboration (useConflictResolution.js) 7.75KB 1.86KB
collaboration (useMentionNotifications.js) 1.81KB 0.68KB
collaboration (usePresence.js) 6.33KB 1.84KB
collaboration (useRealtimeSubscription.js) 7.91KB 2.01KB
components (index.js) 558.59KB 133.88KB
core (index.js) 9.93KB 3.94KB
create-plugin (index.js) 27.94KB 9.51KB
data-objectstack (index.js) 227.61KB 63.16KB
fields (index.js) 261.01KB 66.28KB
i18n (LocalizationContext.js) 1.76KB 0.96KB
i18n (builtinAggregateLabels.js) 0.86KB 0.49KB
i18n (currency.js) 2.59KB 1.22KB
i18n (fallbackInterpolation.js) 6.25KB 2.77KB
i18n (i18n.js) 8.87KB 3.64KB
i18n (index.js) 5.24KB 2.27KB
i18n (pickLocalized.js) 9.86KB 3.95KB
i18n (provider.js) 39.40KB 12.91KB
i18n (translateFn.js) 0.20KB 0.18KB
i18n (useDisplayLocale.js) 3.52KB 1.76KB
i18n (useObjectLabel.js) 34.34KB 9.17KB
i18n (useSafeTranslation.js) 5.60KB 2.33KB
layout (index.js) 39.32KB 11.09KB
mobile (MobileProvider.js) 0.92KB 0.49KB
mobile (ResponsiveContainer.js) 0.94KB 0.38KB
mobile (breakpoints.js) 1.51KB 0.70KB
mobile (createOfflineDataSource.js) 5.61KB 1.75KB
mobile (index.js) 1.99KB 0.87KB
mobile (offlineQueue.js) 3.91KB 1.35KB
mobile (pwa.js) 0.97KB 0.49KB
mobile (serviceWorker.js) 1.48KB 0.62KB
mobile (serviceWorkerSource.js) 3.41KB 1.48KB
mobile (useBreakpoint.js) 1.54KB 0.65KB
mobile (useGesture.js) 6.96KB 1.98KB
mobile (useOfflineSync.js) 1.99KB 0.72KB
mobile (usePullToRefresh.js) 6.62KB 2.45KB
mobile (useResponsive.js) 0.72KB 0.42KB
mobile (useSpecGesture.js) 5.52KB 2.10KB
mobile (useTouchTarget.js) 1.01KB 0.54KB
permissions (MePermissionsProvider.js) 13.52KB 4.88KB
permissions (PermissionContext.js) 0.31KB 0.25KB
permissions (PermissionGuard.js) 0.89KB 0.45KB
permissions (PermissionProvider.js) 6.24KB 2.16KB
permissions (discardProofCache.js) 1.04KB 0.55KB
permissions (evaluator.js) 8.33KB 3.07KB
permissions (index.js) 0.93KB 0.41KB
permissions (store.js) 0.91KB 0.42KB
permissions (useFieldPermissions.js) 1.28KB 0.53KB
permissions (usePermissions.js) 4.83KB 2.27KB
plugin-ai (index.js) 16.01KB 3.93KB
plugin-calendar (index.js) 51.96KB 14.83KB
plugin-charts (index.js) 84.09KB 22.93KB
plugin-chatbot (index.js) 198.22KB 46.97KB
plugin-dashboard (index.js) 137.83KB 36.71KB
plugin-designer (index.js) 215.78KB 44.42KB
plugin-detail (index.js) 233.51KB 61.80KB
plugin-editor (index.js) 2.23KB 1.05KB
plugin-form (index.js) 161.21KB 41.41KB
plugin-gantt (index.js) 170.35KB 42.19KB
plugin-grid (index.js) 228.33KB 62.59KB
plugin-kanban (index.js) 48.43KB 15.11KB
plugin-list (index.js) 115.86KB 28.64KB
plugin-map (index.js) 22.90KB 7.62KB
plugin-markdown (index.js) 13.88KB 4.80KB
plugin-report (index.js) 44.17KB 12.20KB
plugin-timeline (index.js) 31.07KB 9.15KB
plugin-tree (index.js) 11.21KB 3.89KB
plugin-view (index.js) 89.44KB 22.41KB
providers (DataSourceProvider.js) 0.75KB 0.39KB
providers (MetadataProvider.js) 1.37KB 0.59KB
providers (ThemeProvider.js) 1.90KB 0.85KB
providers (UploadProvider.js) 11.81KB 3.58KB
providers (index.js) 0.45KB 0.23KB
providers (types.js) 0.01KB 0.04KB
react-runtime (index.js) 5.62KB 2.34KB
react (LazyPluginLoader.js) 4.47KB 1.63KB
react (SchemaRenderer.js) 119.16KB 39.05KB
react (data-invalidation.js) 5.05KB 2.08KB
react (index.js) 4.03KB 1.86KB
react (schema-input.js) 4.25KB 2.04KB
react (spec-input.js) 0.20KB 0.18KB
sdui-parser (body-dialect.js) 4.78KB 2.09KB
sdui-parser (codegen.js) 7.50KB 3.05KB
sdui-parser (dashboard-widget-options.js) 3.08KB 1.30KB
sdui-parser (index.js) 6.16KB 2.71KB
sdui-parser (input-type.js) 2.84KB 1.40KB
sdui-parser (kanban-quick-add.js) 3.89KB 1.87KB
sdui-parser (parse.js) 25.28KB 7.80KB
sdui-parser (provenance.js) 3.84KB 1.90KB
sdui-parser (types.js) 0.28KB 0.23KB
sdui-parser (validate.js) 18.27KB 6.22KB
types (ai.js) 4.39KB 2.17KB
types (api-types.js) 0.20KB 0.18KB
types (app.js) 3.83KB 1.49KB
types (base.js) 0.20KB 0.18KB
types (blocks.js) 0.20KB 0.18KB
types (complex.js) 2.93KB 1.49KB
types (crud.js) 0.20KB 0.18KB
types (dashboard-filter-alias.js) 6.23KB 2.74KB
types (data-display.js) 3.75KB 1.85KB
types (data-protocol.js) 0.20KB 0.19KB
types (data.js) 0.20KB 0.18KB
types (designer.js) 1.85KB 0.85KB
types (disclosure.js) 0.20KB 0.18KB
types (error-code.js) 1.54KB 0.88KB
types (expression.js) 0.20KB 0.18KB
types (feedback.js) 0.20KB 0.18KB
types (field-types.js) 0.20KB 0.18KB
types (form.js) 0.20KB 0.18KB
types (http-inflight.js) 8.87KB 3.73KB
types (http-retry.js) 4.32KB 2.02KB
types (icon-key-migration.js) 4.26KB 1.63KB
types (index.js) 4.74KB 2.26KB
types (layout.js) 0.20KB 0.18KB
types (managed-by.js) 0.19KB 0.18KB
types (mobile.js) 5.00KB 2.39KB
types (navigation.js) 0.20KB 0.18KB
types (objectql.js) 0.20KB 0.18KB
types (overlay.js) 0.20KB 0.18KB
types (permissions.js) 2.52KB 1.31KB
types (plugin-scope.js) 0.20KB 0.18KB
types (record-components.js) 0.20KB 0.19KB
types (record-semantics.js) 1.28KB 0.67KB
types (registry.js) 0.20KB 0.18KB
types (reports.js) 0.20KB 0.18KB
types (select-option.js) 0.20KB 0.19KB
types (spec-report.js) 5.05KB 1.93KB
types (spec-ui-namespace.js) 0.20KB 0.19KB
types (strict-authoring-face.js) 17.15KB 6.32KB
types (system-fields.js) 3.33KB 1.54KB
types (theme.js) 6.27KB 2.87KB
types (ui-action.js) 8.11KB 3.32KB
types (views.js) 0.20KB 0.18KB
types (widget.js) 0.20KB 0.18KB

Size Limits

  • ✅ Core packages should be < 50KB gzipped
  • ✅ Component packages should be < 100KB gzipped
  • ⚠️ Plugin packages should be < 150KB gzipped

@objectstack-fleet

objectstack-fleet Bot commented Sep 28, 2026 •

Copy link
Copy Markdown
Contributor Author

Standing down on one red check. It is not this PR's. domain:devx seat 2 (session_01EBx9rvB7dufCz4at53x35U), read 2026-09-28T13:42Z.

  • Failing: Spec Main Shape Gate on head 33012bd4d6 (check run 108948805751). The errors are at packages/types/src/__tests__/p1-spec-alignment.test.ts:154 and :164, which author ListViewSchema.tabs.
  • Why it is not this PR's: the diff touches scripts/check-doc-expression-carriage.mjs, its test, CONTRIBUTING.md and packages/vscode-extension/README.md, and nothing under packages/types. The gate compiled against objectstack 6e3e5462c6, which is feat(spec)!: retire the list view's own tabs key; named presets are listViews entries objectstack#20357: feat(spec)! retires the list view's tabs key. Every PR whose gate compiles against that main hits the same two lines.
  • Anchor: objectui#10987, filed by domain:ui seat 2 at 13:41:47Z and claimed by it at 13:44Z as the stop-the-bleed (5871115946). This seat's objectui#10988 lost that filing race by seventeen seconds and is closed duplicate (5871316331).
  • Ported fix: none yet. None exists to port.
  • Plan: once the fix is on objectui main, this seat has main merged into this branch through a dev's git merge, and the gate re-runs on the new head. This PR is watched until it is green and landed. ⛔ No re-run was spent on this failure, because it reproduces by construction.

Generated by Claude Code

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: 33012bd4d69dcc6094493124511757908ce85ba8
Local-runs: none

Inputs: card objectui#10943 (body and all four comments — the correction 5867367228, ruling A 5869344478, the claim 5870313297 as amended to add CONTRIBUTING.md, the os-dev report 5870865414), PR objectui#10985 (body, file list, the net diff), files read at the head and at the merge-base with git show, and the 40 check-runs on the head. The PR base main moved past the merge-base deca847a8a, but none of the four changed files moved with it, so the net diff against main is the PR diff: scripts/check-doc-expression-carriage.mjs +170/−19, scripts/__tests__/check-doc-expression-carriage.test.ts +136/−8, packages/vscode-extension/README.md +1/−1, CONTRIBUTING.md +1/−1; no file added, no workflow touched. The check-runs on the head once none was pending (2026-09-28T13:41Z): 41 runs — 37 success, 3 skipped (the coverage-shard and dependabot placeholders), 1 failure: Spec Main Shape Gate. Their conclusions are the gate verdicts here; nothing was built, run or re-run locally. Build Docs finished in seconds, so its docs filter did not fire and the census CLI step in ci.yml did not run on this head — by that step's own comment a PR that edits only the gate is covered by the vitest pin, which the eight Test shards and the Test aggregate ran green.

The one red is not this diff's, and it blocks the landing all the same. Spec Main Shape Gate compiles objectui against @objectstack/spec built from objectstack main; its seven annotations attribute the break to packages/types/src/__tests__/p1-spec-alignment.test.ts (tabs on a ListViewSchema, isDefault on never) compiled against objectstack@6e3e5462c6d1, the squash of objectstack #20357 「retire the list view's own tabs key; named presets are listViews entries」, which merged at 2026-09-28T13:20Z. This diff touches no file under packages/types, and that test file is blob-identical on main (b4c41d94c8af) and on this head. The workflow's own run listing, read again at 2026-09-28T13:46Z: every run of that gate that started before that merge is green (including main's queue leg for 42687baf20 and PR objectui#10983's PR leg); every run that started after it is red with the same seven annotations — PR objectui#10983's queue leg, this head, PR objectui#10984, PR objectui#10986 and PR objectui#10962's queue leg. The gate's header says its subject is cross-repo drift that moves when no file here changes, and it names the objectstack pull request that moved the shape as the one that answers. The context is enrolled in REQUIRED_CONTEXTS (scripts/dependabot-merge-gate.mjs, under objectui#9969's ruling), so the Tier S landing condition 「every check is green」 is NOT met on this head, and nothing in this diff can meet it: it clears only once an objectui-side fix for the tabs retirement lands on main and this branch merges origin/main (a re-run reuses this head's merge ref and would compile the same unfixed tree). Escalated in ③; this record does not file it. Record posted 2026-09-28T13:47Z.

① Derived judgments

Ruling A's execution parameters, item by item (comment 5869344478):

  • json through the IMPORTED parseJsonFence with no tolerance — RIGHT. parseFence(body, 'json') calls requireJsonContract()(body, 'json'), and requireJsonContract returns the parseJsonFence binding of scripts/check-skill-examples.mjs, whose json branch at the head is JSON.parse(body) and nothing else (read). That branch touches none of sanitizeFence, splitTopLevel, the object-body retry or the multi-document split; the identity pin requireJsonContract() toBe(parseJsonFence) holds it to the same function object, not a copy.
  • jsonc keeps exactly its four tolerances plus the object-body retry, not widened — RIGHT. The non-json path of parseFence at the head is line-for-line the merge-base's parseFence(body) (compared), and the diff does not touch sanitizeFence, dropTrailingCommas, dropElisionStrings or splitTopLevel. The seven tolerance bodies in the new describe block are each pinned to still read under jsonc.
  • unparsed names file, fence line and parse error — RIGHT. analyze pushes { file, line, lang, reason } with reason = invalid-json: plus the contract's own JSON.parse message; the fixture-tree pin asserts the entry at line 7 with the language and that reason.
  • the ruled prescription text for json is printed — RIGHT. UNPARSED_PRESCRIPTIONS.json opens with the ruled sentence verbatim (「retag as jsonc if the example needs comments or trailing commas」); the CLI prints it as Remedy: under the json block and the pin's failure message prints it through unparsedPrescription, one constant for both, and a pin asserts the text.
  • the pin keeps asserting unparsed is empty — RIGHT. it('has no blind spot on the corpus it ships against') still ends in expect(census.unparsed, …).toEqual([]); only the message changed, to carry the remedy per language on the list.
  • fixture pins exist for both tags — RIGHT. A good json fence; seven non-JSON bodies (line comment, block comment, raw newline in a string, trailing comma, elision, object body, two documents) each rejected under json with the contract's own error and read under jsonc; a good jsonc fence with a comment; and a throwaway-tree census with the same annotated body once as json and once as jsonc, of which only the json copy lands on the list, plus a CLI --root run asserting exit 0 and the printed remedy.
  • a planted bad json fence is proven red, then removed — ACCEPTED on the dev's reading, not re-run here. Head leg at 11fc05b98f: the pin RED (Tests 1 failed | 58 skipped (59)) with the received lang: json and the strict reason; base leg at deca847a8a: the identical plant GREEN (1 passed | 46 skipped (47)); both restores verified by blob hash and an empty git diff HEAD. The second commit 33012bd4d6 changes only the import wrapper (10+/9−, no parse-path line), so the reading carries to this head. No plant remains: the diff touches no page under the surface, and the pin is green on the head's Test shards.
  • no new script, workflow or gate, and no other fence language gains a parse check — RIGHT. Four modified files, nothing added, .github/ untouched. The only strict branch is lang === 'json'; parseFenceDialect passes no language and the blind-spot leg still reads unscanned fences tolerantly and judges none (analyze reaches unparsed from fence.scanned only).

The dynamic-import choice — RIGHT, and loud. In substance it is still 「imports parseJsonFence」: import('./check-skill-examples.mjs') and module.parseJsonFence, the same function object, with no re-spelling and no fallback parser (requireJsonContract throws rather than substituting). The shape is one top-level const holding an awaited import(); the reason it is not a static import is measured (a static import would fail at module link in the orphan pin before the CLI prints 'A failure, not a skip'), and the reason it is not a top-level try is check:entry-guard's own self-test row 'a top-level try/catch that runs is rejected' — the Lint job runs that gate and is green on the head. The failure mode is loud in every reader: the CLI calls requireJsonContract() inside its instrument try, so a missing or export-less module exits 1 with the 'A failure, not a skip' line; every parseFence(…, 'json') in scanFences goes through the same function, so the real-tree pin and the identity pin throw instead of skipping; a module that loads but exports no function is refused by the typeof check with a message naming the contract and pnpm install. Cost: the census now loads typescript at import (the dev measured about half a second); Pre-Install Import Graph Check is green, and the census is not a pre-install script (it already needs the built spec).

Docs:

  • packages/vscode-extension/README.md retag to jsonc — RIGHT. The body carries // comments (VS Code settings are JSONC), so jsonc is the honest tag and json was the mistake the convention names; it is the one fence on the whole surface strict json refuses (the dev's H2 reading, base json 221/0 · jsonc 15/0 to head 220/0 · 16/0; the pin's green on the head is the live confirmation). The site page content/docs/utilities/vscode-extension.mdx already tags its settings example jsonc, so the README now agrees with it. Doc Fence Language Check, whose walk includes every packages/NAME/README.md, is green on the head. The package is private: true, outside the changeset fixed group; where the README is rendered, an unrecognised tag costs highlighting at most.
  • CONTRIBUTING.md passage — every sentence true at the head. The census's surface is EXACTLY the check:doc-types walk, by import (its header and SURFACE_LABEL), and parseFence applies the imported function to every fence whose language reads json; a non-parsing json fence lands on census.unparsed (analyze); the pin is named by its verbatim test title and keeps the list empty; it fails the PR because the vitest suite runs on any scripts/** PR and a markdown-only PR reaches it through scripts/markdown-test-inputs.mjs, whose ledger row for this test reads content/docs/**, packages/**, README.md and the console docs. 'The census's comment, raw-newline, trailing-comma and elision tolerances apply to jsonc only' is true of the judged population (the blind-spot leg reads unjudged languages tolerantly and judges nothing — a measurement, not a tolerance a reader meets). The kept sentence that a raw newline in a string is invalid under both tags is true of the contract, see ③.
  • The census's header docblock — true after the change. The exit line names the import failure; the tolerance section says the four apply to jsonc and never to json; the new section records the ruling, the posture (CLI exit 0, the pin is what reds) and the import mechanics; the old prose count ('0 unparsed over the whole tree') is replaced by a pointer at the pin, which is the objectui AGENTS.md 完善设计器的每一个细节 #9 form. Nothing left in the file says a json fence receives a tolerance.

Scan surface: the strictness lands on the census's whole surface — content/docs, apps/*/docs, packages/*/README.md and the root README.md — which is wider than the card title's 「under content/docs」. That is inherent in A's mechanism (a strength increase of the one existing pin, which already walked that surface), it was flagged on the card before the ruling (5867367228), and the ruling's execution parameters narrow nothing to content/docs. The only verdict that moved on the wider legs is the retagged README fence. Nothing on the scan surface is judged differently than the ruling intends.

② Semver level

No changeset is RIGHT. scripts/check-changeset-presence.mjs guards the src/ of the packages in .changeset/config.json's fixed group; scripts/** and CONTRIBUTING.md are outside it, packages/vscode-extension is private: true and not in that group, and a *.md is excluded from the guarded set in any case. Changeset Declaration, Changeset Fixed Group Check and Changeset Claim Re-read are green on the head. The PR's Clause-②: no — … line is well-formed (the value no is the first token after the colon, reasoning after it, no arm) and right: what narrows is the accept set of a repository test pin over the docs corpus, not a published contract, accept set or public surface, so no (narrowing) arm is owed. The new exports requireJsonContract, UNPARSED_PRESCRIPTIONS, unparsedPrescription and the second parseFence parameter are scripts/** internals; the only importers of the module are its own test and markdown-fence-scan.test.ts.

③ Boundary flags

  • open_questions[0] (keep the CONTRIBUTING.md correction in this PR, or split it): the seat answered A and amended the claim's file surface. Judged RIGHT: the sentence 'Nothing enforces it for content/docs today … the open decision objectui#10943' is made false by this very diff, the fix is one sentence, and CONTRIBUTING.md is not a governed surface (Governed Surface Queue Guard green; the dev's check-governed-queue-guard --test reads NOT GOVERNED). Answered.
  • Deviation, guarded dynamic import: answered in ①, right.
  • Deviation, runControls judges its two fixtures as json: CONTROL_FIXTURES.positive and .negative are strict JSON (read), so their verdicts do not move and every run now exercises the imported contract — a strengthening of the controls, right.
  • Deviation, the void first ablation attempt and the vitest startup error: correctly not counted as readings.
  • Deviation, model-free commit trailers: both commits carry Claude-Session: and Co-authored-by: Claude with no model identifier, the form objectstack AGENTS.md prescribes and the pre-push hook enforces; right that the harness reminder yielded.
  • Deviation, no labels written: documentation and tests are labeler-owned (the label check-run); right.
  • Dev note, content/docs/guide/ci-cd-pipeline.md's census row omits the package-README leg: pre-existing (since objectui#8115), not made false by this diff, and the row's 'exits 0 regardless, so it can block no merge' stays true of the CLI it describes while the pin is what blocks. It is a stale enumerated surface, the objectui AGENTS.md 完善设计器的每一个细节 #9 class; carrier none is acceptable for this PR, and the next touch of that row should point at the walk check:doc-types takes instead of listing it. Not a blocker; noted for the seat as a docs follow-up candidate.
  • Dev note, CONTRIBUTING.md says a raw newline in a string is invalid under both tags while the census's jsonc tolerance re-escapes one: the sentence describes the contract (parseJsonFence rejects it under both tags; so do JSON and JSONC), and the passage now says the census's raw-newline tolerance applies to jsonc only, so a reader can see the census is looser than the convention for jsonc. The ruling keeps that list unchanged; no text conflict, no action.
  • Reviewer escalation, outside this diff: Spec Main Shape Gate is red fleet-wide since objectstack #20357 landed (the readings are in the opening paragraph). It is a required context, so this PR — and every objectui PR whose gate runs from now on — cannot land until packages/types follows the tabs to listViews retirement (at least ListViewSchema and p1-spec-alignment.test.ts) on main. Carrier: the objectstack side that moved the shape (#20357) and the objectui domain: seat that owns packages/types; not this card. ⛔ Do not arm auto-merge on this PR while that context is red, and do not read this PASS as the landing condition met.

Implemented-by: claude/issue-10943-strict-json-fence-census
Reviewed-by: session_01EBx9rvB7dufCz4at53x35U

VERDICT: PASS


Generated by Claude Code

…rict-json-fence-census

Claude-Session: https://claude.ai/code/session_01EBx9rvB7dufCz4at53x35U
Co-authored-by: Claude <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

Metric Value Budget
Eager closure (gzip, 330 chunks) 3103.9 KB 3104.5 KB
Main entry chunk (gzip) 149.7 KB 350 KB
Entry file index-q9OI0-wf.js —
Status PASS —

The eager closure is every chunk the entry reaches through static imports — what the browser fetches and parses before the app renders. The entry chunk on its own is a small fraction of it.


📦 Bundle Size Report

Package Size Gzipped
app-shell (consoleActionDispatch.js) 0.20KB 0.19KB
app-shell (index.js) 16.58KB 6.17KB
app-shell (runtime-config.js) 20.68KB 7.36KB
app-shell (types.js) 0.01KB 0.04KB
app-shell (urlParams.js) 10.06KB 3.86KB
auth (ActiveOrganizationStorage.js) 27.95KB 10.04KB
auth (AuthContext.js) 0.31KB 0.24KB
auth (AuthGuard.js) 2.07KB 1.00KB
auth (AuthProvider.js) 40.22KB 10.61KB
auth (AuthShell.js) 3.49KB 1.40KB
auth (ForgotPasswordForm.js) 12.21KB 3.45KB
auth (LoginForm.js) 18.17KB 5.40KB
auth (PreviewBanner.js) 0.90KB 0.50KB
auth (RegisterForm.js) 6.72KB 2.24KB
auth (SocialSignInButtons.js) 9.70KB 3.93KB
auth (UserMenu.js) 3.39KB 1.21KB
auth (auth-gate-events.js) 1.29KB 0.66KB
auth (authStyles.js) 5.04KB 1.72KB
auth (createAuthClient.js) 40.70KB 10.94KB
auth (createAuthenticatedFetch.js) 8.52KB 3.45KB
auth (index.js) 3.63KB 1.64KB
auth (invitation-status.js) 1.22KB 0.70KB
auth (org-roles.js) 6.66KB 2.78KB
auth (phone-identifier.js) 1.11KB 0.66KB
auth (types.js) 0.59KB 0.35KB
auth (useAuth.js) 5.30KB 1.02KB
auth (useWorkspaceAdminStatus.js) 11.08KB 4.58KB
collaboration (CommentThread.js) 27.13KB 7.95KB
collaboration (LiveCursors.js) 3.17KB 1.27KB
collaboration (PresenceAvatars.js) 6.49KB 2.64KB
collaboration (PresenceProvider.js) 2.79KB 1.13KB
collaboration (index.js) 1.68KB 0.73KB
collaboration (useCollaborationTranslation.js) 6.05KB 2.52KB
collaboration (useCommentSearch.js) 1.98KB 0.88KB
collaboration (useConflictResolution.js) 7.75KB 1.86KB
collaboration (useMentionNotifications.js) 1.81KB 0.68KB
collaboration (usePresence.js) 6.33KB 1.84KB
collaboration (useRealtimeSubscription.js) 7.91KB 2.01KB
components (index.js) 558.59KB 133.88KB
core (index.js) 9.93KB 3.94KB
create-plugin (index.js) 27.94KB 9.51KB
data-objectstack (index.js) 227.61KB 63.16KB
fields (index.js) 261.01KB 66.28KB
i18n (LocalizationContext.js) 1.76KB 0.96KB
i18n (builtinAggregateLabels.js) 0.86KB 0.49KB
i18n (currency.js) 2.59KB 1.22KB
i18n (fallbackInterpolation.js) 6.25KB 2.77KB
i18n (i18n.js) 8.87KB 3.64KB
i18n (index.js) 5.24KB 2.27KB
i18n (pickLocalized.js) 9.86KB 3.95KB
i18n (provider.js) 39.40KB 12.91KB
i18n (translateFn.js) 0.20KB 0.18KB
i18n (useDisplayLocale.js) 3.52KB 1.76KB
i18n (useObjectLabel.js) 34.34KB 9.17KB
i18n (useSafeTranslation.js) 5.60KB 2.33KB
layout (index.js) 39.32KB 11.09KB
mobile (MobileProvider.js) 0.92KB 0.49KB
mobile (ResponsiveContainer.js) 0.94KB 0.38KB
mobile (breakpoints.js) 1.51KB 0.70KB
mobile (createOfflineDataSource.js) 5.61KB 1.75KB
mobile (index.js) 1.99KB 0.87KB
mobile (offlineQueue.js) 3.91KB 1.35KB
mobile (pwa.js) 0.97KB 0.49KB
mobile (serviceWorker.js) 1.48KB 0.62KB
mobile (serviceWorkerSource.js) 3.41KB 1.48KB
mobile (useBreakpoint.js) 1.54KB 0.65KB
mobile (useGesture.js) 6.96KB 1.98KB
mobile (useOfflineSync.js) 1.99KB 0.72KB
mobile (usePullToRefresh.js) 6.62KB 2.45KB
mobile (useResponsive.js) 0.72KB 0.42KB
mobile (useSpecGesture.js) 5.52KB 2.10KB
mobile (useTouchTarget.js) 1.01KB 0.54KB
permissions (MePermissionsProvider.js) 13.52KB 4.88KB
permissions (PermissionContext.js) 0.31KB 0.25KB
permissions (PermissionGuard.js) 0.89KB 0.45KB
permissions (PermissionProvider.js) 6.24KB 2.16KB
permissions (discardProofCache.js) 1.04KB 0.55KB
permissions (evaluator.js) 8.33KB 3.07KB
permissions (index.js) 0.93KB 0.41KB
permissions (store.js) 0.91KB 0.42KB
permissions (useFieldPermissions.js) 1.28KB 0.53KB
permissions (usePermissions.js) 4.83KB 2.27KB
plugin-ai (index.js) 16.01KB 3.93KB
plugin-calendar (index.js) 51.96KB 14.83KB
plugin-charts (index.js) 84.09KB 22.93KB
plugin-chatbot (index.js) 198.22KB 46.97KB
plugin-dashboard (index.js) 137.83KB 36.71KB
plugin-designer (index.js) 215.78KB 44.42KB
plugin-detail (index.js) 233.51KB 61.80KB
plugin-editor (index.js) 2.23KB 1.05KB
plugin-form (index.js) 161.21KB 41.41KB
plugin-gantt (index.js) 170.35KB 42.19KB
plugin-grid (index.js) 228.33KB 62.59KB
plugin-kanban (index.js) 48.43KB 15.11KB
plugin-list (index.js) 115.86KB 28.64KB
plugin-map (index.js) 22.90KB 7.62KB
plugin-markdown (index.js) 13.88KB 4.80KB
plugin-report (index.js) 44.17KB 12.20KB
plugin-timeline (index.js) 31.07KB 9.15KB
plugin-tree (index.js) 11.21KB 3.89KB
plugin-view (index.js) 89.44KB 22.41KB
providers (DataSourceProvider.js) 0.75KB 0.39KB
providers (MetadataProvider.js) 1.37KB 0.59KB
providers (ThemeProvider.js) 1.90KB 0.85KB
providers (UploadProvider.js) 11.81KB 3.58KB
providers (index.js) 0.45KB 0.23KB
providers (types.js) 0.01KB 0.04KB
react-runtime (index.js) 5.62KB 2.34KB
react (LazyPluginLoader.js) 4.47KB 1.63KB
react (SchemaRenderer.js) 119.16KB 39.05KB
react (data-invalidation.js) 5.05KB 2.08KB
react (index.js) 4.03KB 1.86KB
react (schema-input.js) 4.25KB 2.04KB
react (spec-input.js) 0.20KB 0.18KB
sdui-parser (body-dialect.js) 4.78KB 2.09KB
sdui-parser (codegen.js) 7.50KB 3.05KB
sdui-parser (dashboard-widget-options.js) 3.08KB 1.30KB
sdui-parser (index.js) 6.16KB 2.71KB
sdui-parser (input-type.js) 2.84KB 1.40KB
sdui-parser (kanban-quick-add.js) 3.89KB 1.87KB
sdui-parser (parse.js) 25.28KB 7.80KB
sdui-parser (provenance.js) 3.84KB 1.90KB
sdui-parser (types.js) 0.28KB 0.23KB
sdui-parser (validate.js) 18.27KB 6.22KB
types (ai.js) 4.39KB 2.17KB
types (api-types.js) 0.20KB 0.18KB
types (app.js) 3.83KB 1.49KB
types (base.js) 0.20KB 0.18KB
types (blocks.js) 0.20KB 0.18KB
types (complex.js) 2.93KB 1.49KB
types (crud.js) 0.20KB 0.18KB
types (dashboard-filter-alias.js) 6.23KB 2.74KB
types (data-display.js) 3.75KB 1.85KB
types (data-protocol.js) 0.20KB 0.19KB
types (data.js) 0.20KB 0.18KB
types (designer.js) 1.85KB 0.85KB
types (disclosure.js) 0.20KB 0.18KB
types (error-code.js) 1.54KB 0.88KB
types (expression.js) 0.20KB 0.18KB
types (feedback.js) 0.20KB 0.18KB
types (field-types.js) 0.20KB 0.18KB
types (form.js) 0.20KB 0.18KB
types (http-inflight.js) 8.87KB 3.73KB
types (http-retry.js) 4.32KB 2.02KB
types (icon-key-migration.js) 4.26KB 1.63KB
types (index.js) 4.74KB 2.26KB
types (layout.js) 0.20KB 0.18KB
types (managed-by.js) 0.19KB 0.18KB
types (mobile.js) 5.00KB 2.39KB
types (navigation.js) 0.20KB 0.18KB
types (objectql.js) 0.20KB 0.18KB
types (overlay.js) 0.20KB 0.18KB
types (permissions.js) 2.52KB 1.31KB
types (plugin-scope.js) 0.20KB 0.18KB
types (record-components.js) 0.20KB 0.19KB
types (record-semantics.js) 1.28KB 0.67KB
types (registry.js) 0.20KB 0.18KB
types (reports.js) 0.20KB 0.18KB
types (select-option.js) 0.20KB 0.19KB
types (spec-report.js) 5.05KB 1.93KB
types (spec-ui-namespace.js) 0.20KB 0.19KB
types (strict-authoring-face.js) 17.15KB 6.32KB
types (system-fields.js) 3.33KB 1.54KB
types (theme.js) 6.27KB 2.87KB
types (ui-action.js) 8.11KB 3.32KB
types (views.js) 0.20KB 0.18KB
types (widget.js) 0.20KB 0.18KB

Size Limits

  • ✅ Core packages should be < 50KB gzipped
  • ✅ Component packages should be < 100KB gzipped
  • ⚠️ Plugin packages should be < 150KB gzipped

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation tests

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Decision] Should a json fence under content/docs that fails JSON.parse block the PR that adds it?

2 participants