You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
`ReadonlyFieldRejectedError`'s error `code` is now an importable constant.
6
+
7
+
The strict-readonly refusal — thrown by `engine.update` and `engine.insert` when `options.strictReadonlyWrites` is set and the payload carried caller-supplied fields the engine would have stripped — already told readers to identify it by `code`. `content/docs/kernel/contracts/data-engine.mdx` says so in its own words: *"Catch it by `code`, not `instanceof`, and read `drops` for the per-reason breakdown"*. Until now the code was an inline string literal with nothing to import, so the only way to FOLLOW that published instruction was to re-spell `'ERR_READONLY_FIELD_REJECTED'` in your own package — which acquires a `check:error-code-provenance` stamp site there and can then drift from what the engine throws with no compile error to say so.
**Why `code` and not `instanceof`.** This package declares both realms in its own `exports` (`import` reaches `dist/index.mjs`, `require` reaches `dist/index.js`), so a consumer holding the other realm's copy of the class gets `instanceof` === false — measured, and silent. A `code` compare is the check that survives crossing that boundary, which is exactly what the documentation has been telling readers to do.
14
+
15
+
**Nothing about the wire changed.** The constant holds text byte-identical to the literal it replaces; the refusal throws the same `code` and the same message as before. Consumers that spell the string themselves keep working unchanged — this adds an affordance, it removes nothing.
16
+
17
+
**`ReadonlyFieldRejectedError` itself was already exported and stays exported.** Unlike the classes converted alongside it on this sweep, both routes are published here, so the class and the constant must name the same refusal; a test pins that they do.
-e 'import { syncedPaths } from "./scripts/sync-release-index-currency.mjs"; for (const p of syncedPaths()) console.log(p);' \
687
+
> "$INDEX_LIST"; then
688
+
echo "::error::could not resolve syncedPaths() from scripts/sync-release-index-currency.mjs, so the release-index half of the release file surface is unknown. Refusing to push."
689
+
exit 1
690
+
fi
691
+
if [ ! -s "$INDEX_LIST" ]; then
692
+
echo "::error::syncedPaths() in scripts/sync-release-index-currency.mjs resolved EMPTY, so the release index would go unstaged even though the version pass can rewrite it. Refusing to push."
693
+
exit 1
694
+
fi
695
+
mapfile -t INDEX_SURFACES < "$INDEX_LIST"
696
+
echo "release-index surfaces declared by syncedPaths() (${#INDEX_SURFACES[@]}):"
697
+
sed 's/^/ /' "$INDEX_LIST"
698
+
676
699
git add -A -- \
677
700
'*package.json' \
678
701
'*CHANGELOG.md' \
679
702
.changeset \
680
703
.objectui-sha \
681
704
packages/spec/src/kernel/protocol-version.ts \
682
705
"${TEMPLATE_SURFACES[@]}" \
683
-
"${DOCS_SURFACES[@]}"
706
+
"${DOCS_SURFACES[@]}" \
707
+
"${INDEX_SURFACES[@]}"
684
708
685
709
STAGED="$(git diff --cached --name-only)"
686
710
if [ -z "$STAGED" ]; then
@@ -689,16 +713,17 @@ jobs:
689
713
fi
690
714
691
715
# Re-check every staged path against the allowlist. The pathspec above is
692
-
# convenience; THIS is the guarantee. Three filters, same allowlist the
716
+
# convenience; THIS is the guarantee. Four filters, same allowlist the
693
717
# pathspec used: the fixed release paths by pattern, then the declared
694
-
# templateand doc surfaces by WHOLE-LINE EXACT match (`-xF`) against the
695
-
# very lists that were staged — so neither derived filter can accept a path
696
-
# its declaration does not name, and neither needs regex-escaping of the
697
-
# paths to stay exact.
718
+
# template, doc and release-index surfaces by WHOLE-LINE EXACT match
719
+
# (`-xF`) against the very lists that were staged — so no derived filter
720
+
# can accept a path its declaration does not name, and none needs
echo "::error::the version commit would carry paths outside the release file surface. Refusing to push. Offending paths follow; if the version pass legitimately grew a new output, widen the allowlist in this workflow deliberately."
704
729
printf '%s\n' "$BAD" | sed 's/^/::error:: unexpected: /'
-e 'import { syncedPaths } from "./scripts/sync-release-index-currency.mjs"; for (const p of syncedPaths()) console.log(p);' \
555
+
> "${INDEX_LIST}"; then
556
+
echo "::error::could not resolve syncedPaths() from scripts/sync-release-index-currency.mjs, so the release-index half of the post-version surface is unknown. Refusing to call this tree validated."
557
+
exit 1
558
+
fi
559
+
if [ ! -s "${INDEX_LIST}" ]; then
560
+
echo "::error::syncedPaths() in scripts/sync-release-index-currency.mjs resolved EMPTY, so the release index the version pass rewrites would read as an unexpected path. Refusing to call this tree validated."
561
+
exit 1
562
+
fi
563
+
564
+
# Same four filters cut-rc.yml applies to the same surface: the fixed
565
+
# release paths by pattern, then the three declared lists by WHOLE-LINE
566
+
# EXACT match, so no derived filter can accept a path its declaration
printf '%s\n' "${UNEXPECTED}" | sed 's/^/::error:: unexpected: /'
551
574
echo "::error::the version pass wrote outside the reviewed post-version surface (paths above). This is the treadmill guard: a new version-time output must arrive together with the gate that judges it. Add the surface to the declaration its rewriter reads, and its gate to the content half below — deliberately, in one reviewed diff."
Copy file name to clipboardExpand all lines: content/docs/releases/index.mdx
+1-1Lines changed: 1 addition & 1 deletion
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -18,7 +18,7 @@ migration steps, then covers new capabilities and notable fixes.
18
18
19
19
## Versions
20
20
21
-
- [v17.0.0](/docs/releases/v17) — Files become owned `sys_file` records with server-enforced `accept`/`maxSize` and a governed download path, bulk export becomes its own opt-in privilege, the SDK is reconciled against the routes the server actually mounts (21 dead methods out, 40+ real ones in), approval nodes route approvers dynamically via CEL expressions and decision outputs, a datasource that cannot connect fails the boot, and Node 22 becomes the supported floor; 17.1 adds partial field masking, record-view auditing on `sys_audit_log`, and a per-object read-only approval visibility tier — and makes a deactivated permission set or position actually stop granting access, withdraws the bulk-export wildcard from the shipped admin sets, and gives all three flow doors one honest HTTP status table; 17.2 tightens by-id `update`/`delete` against a silently-dropped `where` predicate or a mismatched id, retires `sys_position.permissions` and other dead ADR-0049 surfaces, and stops analytics from answering the wrong number on a cross-object filter (current series: 17.2.0, released 2026-08-23).
21
+
- [v17.0.0](/docs/releases/v17) — Files become owned `sys_file` records with server-enforced `accept`/`maxSize` and a governed download path, bulk export becomes its own opt-in privilege, the SDK is reconciled against the routes the server actually mounts (21 dead methods out, 40+ real ones in), approval nodes route approvers dynamically via CEL expressions and decision outputs, a datasource that cannot connect fails the boot, and Node 22 becomes the supported floor; 17.1 adds partial field masking, record-view auditing on `sys_audit_log`, and a per-object read-only approval visibility tier — and makes a deactivated permission set or position actually stop granting access, withdraws the bulk-export wildcard from the shipped admin sets, and gives all three flow doors one honest HTTP status table; 17.2 tightens by-id `update`/`delete` against a silently-dropped `where` predicate or a mismatched id, retires `sys_position.permissions` and other dead ADR-0049 surfaces, and stops analytics from answering the wrong number on a cross-object filter (current series: 17.3.0, released 2026-09-04).
22
22
-[v16.0.0](/docs/releases/v16) — One org identifier (`organizationId`) across hooks and actions, quorum + per-group sign-off (会签) approvals with metadata-declared decision actions, time-relative automations, filtered roll-ups, strict dashboard widgets, an identity-scoped MCP stdio transport, and a platform-wide enforce-or-remove sweep that makes dead metadata loud; 16.1 adds a `requires` capability-provider preflight, two more dashboard build gates, and `runAs:'user'` automations that run with the triggering user's real grants (final release: 16.1.0).
23
23
-[v15.0.0](/docs/releases/v15) — Explain record access layer by layer, a docked AI workspace in the Console, project-ready Gantt charts, and phone sign-in; 15.1 adds permission-following attachments, no-code third-party connectors, dashboard-wide filters, pinyin search, and whole-record inline editing — with materially safer multi-tenant and write-path defaults (final release: 15.1.1).
24
24
-[v14.0.0](/docs/releases/v14) — ADR-0090 vocabulary convergence completed, object `enable.*` flags become real gates, admin user management, phone/SMS auth, book-audience enforcement, data-lifecycle contract, and effective-dated grants (final release: 14.8.0).
0 commit comments