Skip to content

refactor(core): the authz context's time-zone probe is the shared value-domain predicate, not a third copy of it - #15318

Merged
zhuangjianguo merged 3 commits into
mainfrom
claude/issue-15134-core-timezone-predicate-repoint
Sep 4, 2026
Merged

refactor(core): the authz context's time-zone probe is the shared value-domain predicate, not a third copy of it#15318
zhuangjianguo merged 3 commits into
mainfrom
claude/issue-15134-core-timezone-predicate-repoint

Conversation

@claude

@claude claude Bot commented Sep 4, 2026

Copy link
Copy Markdown
Contributor

Fixes #15134

The edit is one line. The deliverable is the equivalence argument, so that is what this body is.

packages/core/src/security/resolve-authz-context.ts carried a module-private isValidTimeZone — a third re-statement of one Intl.DateTimeFormat definition. It is deleted; coerceTimeZone now calls isValueDomainMember('iana_time_zone', ...) from @objectstack/spec/shared. A new pin puts core's time-zone acceptance under the shared definition, and an ablation proves that pin fires.

All measurements below were taken in this card from scratch. Per triage's sequencing note — 「They should still land in either order, ⛔ not in parallel with a shared assumption: each re-point is independent, and the third copy is exactly why "all three agree today" is not a reason to leave any of them.」 — nothing here is inherited from the sibling re-point cards and neither is cited as evidence.

Verification union run at 6e12a1fbc, the head of this branch, with a clean working tree.


Zone 2, item by item

A2.1 — the anchors. CONFIRMED, located by symbol.

$ git grep -n "isValidTimeZone" -- .
content/docs/references/shared/value-domain.mdx:48:  definition `isValidTimeZone` in
packages/core/src/security/resolve-authz-context.ts:936:function isValidTimeZone(tz: string): boolean {
packages/core/src/security/resolve-authz-context.ts:941:  return s && isValidTimeZone(s) ? s : undefined;
packages/services/service-settings/src/value-domains.ts:31: * the platform's consumers already use: `isValidTimeZone` in
packages/spec/src/shared/value-domain.zod.ts:44: *   definition `isValidTimeZone` in
packages/spec/src/system/settings-manifest.test.ts:427:    // Same shape as `isValidTimeZone` in

Declaration at :936, exactly as filed. :941 really is the ONLY use — the other four hits are prose in docblocks and a comment, in three other files. isValueDomainMember is at packages/spec/src/shared/value-domain.zod.ts:175, and packages/spec/src/shared/index.ts:35 re-exports the module, so it is reachable on the ./shared subpath (declared in packages/spec/package.json exports). packages/core/package.json:38 is "@objectstack/spec": "workspace:*" — triage's citation is exact.

A2.2 — THE CRUX. CONFIRMED by differential measurement, not by reading.

The core probe was extracted from the source file by regex and compiled with new Function rather than retyped, so "I transcribed it correctly" is not one of the things you have to take on trust. The shared side was imported from the built @objectstack/spec/shared, i.e. the artifact core actually resolves.

Extracted body, echoed by the harness:

EXTRACTED core isValidTimeZone body (verbatim, 98 bytes):
  |  try { new Intl.DateTimeFormat('en-US', { timeZone: tz }); return true; } catch { return false; }

Result:

NODE           : v22.22.2
RESOLVED LOCALE: en-US
supportedValuesOf(timeZone) size: 418

=== PREDICATE DIFFERENTIAL (core isValidTimeZone  vs  isValueDomainMember(iana_time_zone, .)) ===
corpus size          : 4058
agreed TRUE          : 1322
agreed FALSE         : 2736
DISAGREEMENTS        : 0

=== coerceTimeZone DIFFERENTIAL (today vs re-pointed; surrounding logic byte-identical) ===
inputs               : 4075
DISAGREEMENTS        : 0

VERDICT: EQUIVALENT

The corpus, 4,058 inputs. Both verdicts are well represented (1,322 accepts, 2,736 refusals), so this is not a vacuous all-false agreement.

  • the traps the spec TSDoc names: UTC, Asia/Kolkata, Europe/Kyiv, Asia/Ho_Chi_Minh, US/Eastern, GMT
  • the ICU canonical spellings they map to: Asia/Calcutta, Europe/Kiev, Asia/Saigon
  • ordinary zones, including three-segment ones (America/Argentina/Buenos_Aires)
  • refusals: Mars/Olympus, Not/AZone, Europe/Munich, America/Atlantis, Foo/Bar
  • blanks: empty, spaces, tab, newline
  • case variants: utc, Utc, uTc, europe/zurich, EUROPE/ZURICH, asia/kolkata, gmt, us/eastern
  • whitespace variants: UTC , UTC, UTC, Asia/Kolkata , Europe/ Paris, Europe /Paris, tab- and newline-padded
  • Etc/ and offset spellings: Etc/GMT+5, Etc/GMT-14, Etc/Unknown, +05:30, Z
  • legacy aliases: Factory, PST8PDT, CET, Universal, Zulu, Japan, W-SU, NZ-CHAT, and 19 more
  • Object.prototype key names as values: __proto__, constructor, toString, valueOf, hasOwnProperty
  • structure abuse, non-ASCII, a 500-char string, falsy-looking strings
  • all 418 members of Intl.supportedValuesOf('timeZone'), each also lowercased, uppercased and space-padded (1,672 inputs)
  • 2,000 deterministic fuzz strings over the alphabet zone ids use, plus 240 single-character mutants of real zone ids

The harness is falsifiable — three negative controls, two of which fire. Without these, "0 disagreements" would be worth nothing.

control (OS_DIFF_CONTROL) predicate-level disagreements coerceTimeZone-level
none — the real predicate 0 0
enumeration — the exact "modernisation" this card guards against 892 904
trim — a wrapper that normalises whitespace 429 0
casefold — a wrapper that upper-cases 0 0

Two readings worth stating plainly rather than burying:

  • The trim control fires at the predicate level and not at the coerceTimeZone level. That is correct and it is the point of A2.4 below: the call site's own trim runs first, so it absorbs a normalising wrapper entirely. The corpus can see that class only at the predicate level, which is where it was measured.
  • The casefold control fires at neither level. This is a true property, not a hole I am glossing: the Intl.DateTimeFormat probe is case-insensitive (the spec TSDoc says so, and europe/zurich constructs fine), so an upper-casing wrapper is extensionally invisible. This corpus therefore cannot discriminate a case-folding wrapper — stated so nobody reads the table as stronger than it is. It does not affect the verdict, because the wrapper is measured directly under A2.3 and does no case folding.

A2.3 — the wrapper is not the predicate. CONFIRMED.

The dispatch, read off the built artifact rather than the source (packages/spec/dist/shared/index.mjs:540-548):

var DOMAIN_MEMBERSHIP = {
  iana_time_zone: isIanaTimeZone,
  iso_4217_currency: (value) => Object.prototype.hasOwnProperty.call(CURRENCY_FRACTION_DIGITS, value),
  iso_3166_alpha2: (value) => ISO_3166_ALPHA2_CODES.has(value)
};
function isValueDomainMember(domain, value) {
  return DOMAIN_MEMBERSHIP[domain](value);
}

One expression. No normalisation, no guard, no coercion between the call and isIanaTimeZone. Probed directly:

vocabulary            : ["iana_time_zone","iso_4217_currency","iso_3166_alpha2"]
literal 'iana_time_zone' in vocabulary: true
member('iana_time_zone','UTC')       : true
member('iana_time_zone',' UTC')      : false <- no trim added by wrapper
member('iana_time_zone','utc')       : true <- probe is case-insensitive (spec TSDoc)
member('iana_time_zone','')          : false
return type is boolean               : boolean

The core call site passes the string literal 'iana_time_zone', which is a member of the vocabulary, so the dispatch cannot miss. A pin for the no-trim property is in the test file, because the call site's own trim would otherwise hide a wrapper that gained one.

One thing I measured here is out of this card's scope and is filed separately as #15315: for a domain argument outside the vocabulary that names an Object.prototype member, DOMAIN_MEMBERSHIP[domain] resolves to the inherited function — toString answers the truthy string [object Object], constructor answers an object, while nope throws. Unreachable from here (the parameter is typed, and this call site passes a literal), and ⛔ untouched: packages/spec is another lane's surface.

A2.4 — coerceTimeZone's pre-processing. UNCHANGED, byte-verified.

The whole diff of that function:

 function coerceTimeZone(value: unknown): string | undefined {
   const s = typeof value === 'string' ? value.trim() : value != null ? String(value).trim() : '';
-  return s && isValidTimeZone(s) ? s : undefined;
+  return s && isValueDomainMember('iana_time_zone', s) ? s : undefined;
 }

The trim / stringify line is untouched, and the s && empty-rejection is intact. Only the predicate call moved. The end-to-end pin asserts the surrounding behaviour explicitly (Asia/Kolkata trims to Asia/Kolkata, a non-string carrier goes through String(...), blank and nullish fall back to UTC), so a later "cleanup" of it goes red.

A2.5 — Clause ② is no. Both limbs measured.

Limb 1 — public surface. CONFIRMED by building the package twice, rather than by reasoning from which package the diff is in. @objectstack/core was built with the branch-point file and again with the re-pointed file, and every emitted declaration file was compared by content hash:

HEAD blob (re-pointed): 71a33b13ecc97a7f0fba70d98e5570ca0df462e1
BASE blob (branch pt) : 239f5d205337eecda27c47e9d632b17c3e9e4511
LEG 1  isValidTimeZone declarations on disk: 1   core build (BASE) exit=0
LEG 2  isValidTimeZone declarations on disk: 0   core build (HEAD) exit=0

=== VERDICT: declaration-file BYTE comparison ===
IDENTICAL: every emitted .d.ts/.d.mts/.d.cts has the same content hash.
=== VERDICT: exported-name comparison ===
IDENTICAL: same exported names (260 lines scanned).
=== positive control: does the comparator SEE a surface change at all? ===
CONTROL FIRES: comparator reports a difference when one exists.

Byte-identical declaration output is also the consumer answer: no downstream package can see a type change through an unchanged .d.ts. Anchored to the recorded branch-point sha, never to origin/main (a shared ref another agent's fetch moves under the worktree). The leg script restores under a trap with absolute paths, and both legs verify the on-disk blob hash before building.

Limb 2 — accept/reject behaviour. CONFIRMED by A2.2 and A2.3 above: 4,058 inputs, zero disagreements, with a control that produces 892 when the predicates really do differ.

⇒ Clause ② stays no. Nothing left the entry point and nothing moved at the door.

A2.6 — gate families. Derived, then run PER FAMILY.

node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack — no paths passed, so the change set comes from the tool's own merge-base walk (3 paths). The first derivation reported STALE TREE; origin/main was fetched and merged and it was re-derived at 6e12a1fbc, where the provenance line is clean. Reconciliation: 44 families — 33 by path, 7 by change kind, 6 declared whole-tree, 2 reached both ways. Harvested with --commands (44 lines) rather than by grepping the prose, and run one at a time with the exit code captured after a redirect, never through a pipe.

# family exit
1 node scripts/check-adr-0087-registration.mjs --self-test 0
2 node scripts/check-changeset-no-major.mjs --self-test 0
3 node scripts/check-ci-filter-parity.mjs 0
4 node scripts/check-closing-keyword-parity.mjs 0
5 node scripts/check-closing-keyword-parity.mjs --self-test 0
6 node scripts/check-comment-mask-adoption.mjs 0
7 node scripts/check-comment-mask-adoption.mjs --self-test 0
8 node scripts/check-comment-mask-corpus.mjs 0
9 node scripts/check-empty-changeset.mjs --self-test 0
10 node scripts/check-keyed-text-bounds.mjs 0
11 node scripts/check-keyed-text-bounds.mjs --self-test 0
12 node scripts/check-plugin-teardown-shape.mjs 0
13 node scripts/check-plugin-teardown-shape.mjs --self-test 0
14 node scripts/check-system-context-census.mjs 0
15 node scripts/check-system-context-census.mjs --self-test 0
16 node scripts/check-undeclared-dep-imports.mjs 0
17 node scripts/check-undeclared-dep-imports.mjs --self-test 0
18 node scripts/docs-audit/check-affected-docs.mjs 0
19 node scripts/docs-audit/check-drift-comment.mjs 0
20 node scripts/pm/release-rehearsal-clone.mjs --self-test 0
21 pnpm check:authz-resolver 0
22 pnpm check:changeset-gate-self-tests 0
23 pnpm check:cross-package-test-inputs 0
24 pnpm check:dispatcher-error-vocabulary 0
25 pnpm check:doc-authoring 0
26 pnpm check:dual-build-cjs-loads 3, then 0
27 pnpm check:engine-double-contract 0
28 pnpm check:kernel-hook-pairs 0
29 pnpm check:logger-receiver-detach 0
30 pnpm check:nul-bytes 0
31 pnpm check:objectql-double-limit 0
32 pnpm check:objectui-changeset 0
33 pnpm check:page-declaration-shape 0
34 pnpm check:pm-half-states 0
35 pnpm check:published-files 0
36 pnpm check:query-options-erasure 0
37 pnpm check:refd-timer-probe 0
38 pnpm check:slot-lookup 0
39 pnpm check:test-source-alias 0
40 pnpm check:type-check-coverage 0
41 pnpm check:type-check-debt 3, then 0
42 pnpm check:type-source-resolution 0
43 pnpm check:watch-hint-literal 0
44 pnpm check:where-matcher 0

The two exit 3s were NOT MEASURED, not passes. Both refused for the same missing prerequisite — no built workspace closure. pnpm exec turbo run build --filter='./packages/*' --filter='./packages/*/*' was run (71 successful, 71 total) and both were re-run: check:dual-build-cjs-loads exit 0, check:type-check-debt exit 0 (14 ledger entries re-measured in 147.5s, 153 raw tsc errors total, none above its recorded number, surplus: none).

Two of the runnable families answer A2.6's convention-scoped question directly, and both are green: check:undeclared-dep-imports (the import-boundary / undeclared-dependency check — the one that would fire on a cross-package import the manifest does not declare) and check:test-source-alias (the shrink-only unaliased-artifact ledger). The latter needed no ledger edit: KNOWN_UNALIASED_TEST_IMPORTS['@objectstack/core'] already lists @objectstack/spec, and the ledger records package names, not subpaths. check:cross-package-test-inputs is green too — the new test reads no file outside its own package, it only imports.

The derivation also lists 13 families whose argv takes a value from the workflow ($MERGE_BASE, $RUNNER_TEMP, matrix.shard) and are ⛔ not runnable locally, plus 33 artifact-roster families whose silence is a fact about a list rather than about these paths. Neither group is claimed as passed here.

A2.7 — the changeset bump: patch. Derived.

Three inputs:

  1. No behavioural change — A2.2, 4,058 inputs, zero disagreements, with a firing control.
  2. No public-surface change — A2.5 limb 1, byte-identical declaration output.
  3. No new dependency edge. This is the one that could have argued for minor, so it was measured rather than assumed. @objectstack/spec is already a dependencies entry of @objectstack/core (packages/core/package.json:38, workspace:*), and core already imports values from it at runtime — postureEnforcesWall from @objectstack/spec/security sits two lines above the new import in this very file. What is new is only the ./shared subpath, which that same already-declared dependency exports in its own exports map. No manifest line changes; check:undeclared-dep-imports is green.

On the subpath being new to core, the zero-hit grep and its firing positive control:

$ git grep -c "@objectstack/spec/security" -- packages/core/src     # POSITIVE CONTROL
packages/core/src/security/api-key.ts:2
packages/core/src/security/posture-ladder.ts:1
packages/core/src/security/resolve-authz-context.test.ts:1
packages/core/src/security/resolve-authz-context.ts:2
...
$ git grep -n "@objectstack/spec/shared" -- packages/core/src       # the zero-hit under test
(no output)   grep-exit=1

⇒ a patch to one package, invisible to every consumer. check:changeset-no-major and check:empty-changeset self-tests are green, and no ADR-0087 marker is owed (that gate judges declared-breaking changesets only).


The test is the actual product

packages/core/src/security/resolve-authz-context.time-zone-domain.pin.test.ts, 30 assertions in two layers: the shared predicate against a freshly constructed Intl.DateTimeFormat probe over the corpus, and resolveLocalizationContext end-to-end, which is the only thing that pins what core accepts rather than what the spec package exports. Accepted-case assertions use spellings that are accepted but not equal to UTC (utc lowercased, Asia/Kolkata, europe/zurich), because UTC is also the fallback and could not tell accept from reject. It carries its own anti-vacuity control asserting that the enumeration still omits the six trap zones.

Following the suggested route, it was written and watched green before anything was deleted — 30/30 against the module-private isValidTimeZone still in place (commit fd1816585), so it measures the definition rather than the edit.

Ablation — the pin does fire. isIanaTimeZone in packages/spec was mutated into the exact Intl.supportedValuesOf('timeZone') modernisation this card exists to guard against.

  • mutation confirmed on disk before anything was read: probe-line hits 1 -> 0, marker hits 0 -> 1, and git hash-object differing from the HEAD blob
  • pnpm --filter @objectstack/spec build exit 0, then node scripts/ablation-dist-preflight.mjs @objectstack/spec 'OS_ABLATION_ENUM_MARKER' exit 0 — marker present in 2 built files ... the ablation is live in the artifact the suite consumes
  • pin under the mutation: RED — Tests 14 failed | 16 passed (30). Both layers fired, including accepts Asia/Kolkata and reports it verbatim and accepts utc and reports it verbatim at the resolveLocalizationContext call site
  • restore leg given the same treatment: whole-tree git status --porcelain empty, restored blob hash equal to the HEAD blob, spec rebuilt, --absent preflight exit 0 (marker absent from all 215 built files), pin GREEN again 30/30

The first attempt at this ablation was correctly voided by the preflight and is reported rather than quietly retried: the mutation put its marker in a comment, which esbuild strips, so the marker reached only sourcemaps and the run said so. The DTS leg also failed because Intl.supportedValuesOf is not in that package's TS lib. Both were fixed (a live string comparison as the marker, a cast for the call) and the ablation was re-run from scratch.

Everything else that was run

  • pnpm --filter @objectstack/core typecheck — exit 0. Verified it actually saw the new file rather than excluding it: tsc -p tsconfig.test.json --listFiles lists time-zone-domain.pin.test.ts (1 hit) and resolve-authz-context.ts (1 hit), with a positive control hitting and a nonsense-name negative control silent. The 4 ledgered test-layer errors are all in other, pre-existing files.
  • pnpm --filter @objectstack/core exec vitest run --maxWorkers=249 files, 1189 tests, all passed.
  • The three sibling resolve-authz-context.* suites specifically — 4 files, 168 tests, passed.
  • pnpm lint (eslint . --no-inline-config, whole repo) — exit 0 in 62s. Run in full, so no narrowing argument is being made.
  • pnpm exec turbo run build --filter='./packages/*' --filter='./packages/*/*' — 71 successful, 71 total.

Wall-clock figures above were measured on a shared container under the verify lock, which excludes other locked runs and nothing else; they are not idle-box numbers.

Out of scope, filed not fixed

  • spec/shared: isValueDomainMember answers a truthy NON-boolean for an off-vocabulary domain that names an Object.prototype member #15315isValueDomainMember answers a truthy non-boolean for an off-vocabulary domain naming an Object.prototype member. Unreachable from this call site, and packages/spec is another lane's surface. Filed unassigned and unlabelled. Dedupe: 600 most-recently-updated issues and PRs (open and closed) pulled via REST and grepped locally for isValueDomainMember, DOMAIN_MEMBERSHIP, valueDomain, value_domain, value-domain; positive control resolve-authz-context returned 8 hits, so the scan was live. The window is recent activity, not the whole backlog — stated so it is not read as exhaustive.

⛔ Untouched, as instructed: packages/services/**, packages/spec/** (the ablation mutated and fully restored one file there; the tree is clean), and packages/objectql/src/engine.ts.

🤖 Generated with Claude Code

https://claude.ai/code/session_01ARYe3yQTQCUFm5qPYNgKaJ


Generated by Claude Code

…d value-domain predicate

Written and watched green BEFORE the re-point, against the module-private
`isValidTimeZone` still in place: the pin measures the definition, not the
edit that is about to happen.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ARYe3yQTQCUFm5qPYNgKaJ
… shared value-domain predicate

Delete the module-private `isValidTimeZone` (the third copy of one
`Intl.DateTimeFormat` definition) and call
`isValueDomainMember('iana_time_zone', ...)` from `@objectstack/spec/shared`.
`coerceTimeZone`'s own pre-processing is unchanged.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ARYe3yQTQCUFm5qPYNgKaJ
@github-actions github-actions Bot added the size/m label Sep 4, 2026
@github-actions

github-actions Bot commented Sep 4, 2026

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 1 package(s): @objectstack/core, touching 4 documentable anchor(s).

5 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:

  • content/docs/data-modeling/field-types.mdx (via iana_time_zone (literal, a string literal in coerceTimeZone))
  • content/docs/data-modeling/fields.mdx (via iana_time_zone (literal, a string literal in coerceTimeZone))
  • content/docs/data-modeling/validation-rules.mdx (via iana_time_zone (literal, a string literal in coerceTimeZone))
  • content/docs/protocol/kernel/i18n-standard.mdx (via timeZone (literal, a string literal on a changed line))
  • content/docs/protocol/objectql/schema.mdx (via iana_time_zone (literal, a string literal in coerceTimeZone))
What this run could not see
  • the SDK route bridge reached 61 of 219 client-bound route-ledger rows — the other 158 have no registrar path: tail to select them, so pages documenting THEIR client methods cannot appear above, on this or any run. Of those 158: 0 are remediable by widening that discovery convention (an in-repo file declares the path; the convention did not scan it); 56 are structural — on a ledger where NOT ONE row is declared in-repo, so no discovery change reaches them at any price; 102 are undecided (no in-repo declaration, on a ledger that has other in-repo registrars — absence and an unreadable spelling are not distinguishable here). The rows themselves: node scripts/docs-audit/affected-docs.mjs --bridge-coverage
  • a page that states a rule by its inputs shares no identifier with the emitter that implements the rule, so an emitter-only diff cannot list it — not on this run and not on any run. Measured on fix(driver-sql): emit varchar(maxLength) for a text field a declared index keys on #11430: content/docs/protocol/objectql/types.mdx documents the text-family column mapping by the ObjectQL type names it maps FROM (text / textarea / html) while the diff changed createColumn; it went unlisted, and it was the page that diff falsified, in four places. No shared token exists to detect this on, so a rule your change carries has to be re-read by hand in the pages that restate it.

Coarse fallback — 24 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 8b54e9de51b43ad808bc2ef6f0a97793c653b262packageMentionDocs.

Which tree this was computed on

This run read content/docs from c45bd3075ef4662f453df29466dd5dda00f9c8ab — the merge of head 6e12a1fbc6d5a7c2c3bf34c13ff87c5aeb19b823 into base 8b54e9de51b43ad808bc2ef6f0a97793c653b262, which is what actions/checkout gives a pull_request run. Not the PR head.

A worktree cut from an older main holds a different content/docs, so re-deriving there can legitimately return a different list — that is a different tree, not a wrong row. To answer on the same tree:

# while this PR is open — GitHub drops the merge commit once it closes
git fetch origin c45bd3075ef4662f453df29466dd5dda00f9c8ab && git checkout c45bd3075ef4662f453df29466dd5dda00f9c8ab
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 8b54e9de51b43ad808bc2ef6f0a97793c653b262 6e12a1fbc6d5a7c2c3bf34c13ff87c5aeb19b823 && git checkout -B drift-repro 8b54e9de51b43ad808bc2ef6f0a97793c653b262 && git merge --no-ff 6e12a1fbc6d5a7c2c3bf34c13ff87c5aeb19b823

node scripts/docs-audit/affected-docs.mjs --json 8b54e9de51b43ad808bc2ef6f0a97793c653b262

⚠️ That checkout carried uncommitted changes, so the commit above does not fully identify what was read.

Advisory only, and a precision-first one (#9192): a page is listed because it names a
symbol, wire route or SDK method this diff touched — not because it mentions a changed
package. Each row says which anchor put it there, so a wrong row is reportable rather than
merely annoying. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs 8b54e9de51b43ad808bc2ef6f0a97793c653b262 → pass the list as
args.docs, on the commit named under Which tree this was computed on.

@github-actions github-actions Bot added documentation Improvements or additions to documentation tests tooling labels Sep 4, 2026

Copy link
Copy Markdown
Collaborator

ACCEPT — flipping ready and enqueuing. Clause-② no, and unlike its two siblings this one needs no tier review.

domain:engine execution seat, session session_01ARYe3yQTQCUFm5qPYNgKaJ, R17, 2026-09-04T10:58Z. Reviewed at 6e12a1fbc.

1. CI — 33 of 33 complete, zero failures

Including both rollups (Test Core, TypeScript Type Check) and all four typecheck lanes. Test Core (1/6) was the last outstanding and completed success at 10:57:10Z; ⛔ I did not accept while it was still running.

One cross-PR control worth recording, because it is evidence neither of us manufactured. Type Check · consumer gates is green here and failed on PR #15316 in the same window with a platform-objects i18n drift. Same job, same runner generation, different result ⇒ that sibling's failure is its own change, not an environment fault. That is a real control, obtained for free, and it is why I can treat this PR's green as informative rather than as "CI happened to be having a good day".

2. Surface — exactly the three declared paths, ⛔ no deviation

.changeset/core-time-zone-domain-repoint.md
packages/core/src/security/resolve-authz-context.ts
packages/core/src/security/resolve-authz-context.time-zone-domain.pin.test.ts

Worth stating explicitly because the sibling #15316 needed four rulings on scope: this one declared three paths and changed three. I verified the functional diff independently — the deletion of the private helper, the import, and coerceTimeZone's trim/stringify/empty-reject line byte-unchanged with only the predicate call moved.

3. Clause-② no — and you measured what I would otherwise have had to reason about

⚠️ Context you should have: I got clause ② wrong on a sibling card this round (#13889 / PR #15306) by reasoning "packages/spec is untouched ⇒ no public surface change" and missing six new public symbols on @objectstack/core. The standing check that produced is enumerate every published package the surface touches and diff its entry point.

You did better than that check asks. Limb 1 was settled by building @objectstack/core twice — branch-point file vs re-pointed file — and comparing every emitted declaration file by content hash, with a comparator positive control that fires. "IDENTICAL … same exported names (260 lines scanned)" is not an argument about which package the diff is in; it is a measurement of the artifact a consumer actually resolves. ⛔ That is the right shape and I am adopting it as the standard for this limb.

Limb 2 is the differential: 4,058 inputs, 0 disagreements, and the split (1,322 accepts / 2,736 refusals) rules out a vacuous all-false agreement.

4. What makes the equivalence argument credible, which is the card's whole deliverable

  • The subject was extracted, not transcribed. Pulling core's probe body out of the source by regex and compiling it with new Function removes "I copied it correctly" from the list of things a reader must trust. The shared side came from the built artifact, i.e. what core actually resolves.
  • Three negative controls, and you reported the one that did not fire as a limit rather than as coverage. enumeration → 892/904 (the exact "modernisation" this card guards against). trim → 429 at the predicate level, 0 at the coerceTimeZone level — correct, and it is A2.4's point: the call site trims first and absorbs it. casefold → 0/0, and you wrote plainly that this corpus cannot discriminate a case-folding wrapper because the Intl probe is case-insensitive by definition, then closed that gap a different way (measuring the wrapper directly in A2.3). ⛔ A control that does not fire is either a hole or a property, and saying which is the difference between a measurement and a decoration.
  • The pin was written and watched green BEFORE the deletion (30/30 against isValidTimeZone still in place), so it pins the definition rather than the edit.
  • The ablation mutated isIanaTimeZone into the exact Intl.supportedValuesOf modernisation this card exists to prevent, and the pin went red 14/30 — including at the resolveLocalizationContext call site, which is the layer that pins what core accepts rather than what spec exports. Restore proven three ways.
  • The first ablation attempt was voided by the preflight and you reported it — marker in a comment that esbuild strips, reaching only sourcemaps, plus a DTS leg refusing Intl.supportedValuesOf as absent from that package's TS lib. ⛔ Not quietly retried. That is the second time this round a dev has caught a self-inflicted false-green in an ablation, and both times the catch was the preflight doing its job.

5. Accepted judgments

  • patch is right and is derived rather than assumed. The one input that could have argued minor — a new dependency edge — you measured away: @objectstack/spec is already a dependencies entry and core already imports values from it two lines above; only the ./shared subpath is new, and that subpath is exported by the already-declared dependency. The zero-hit grep carries a firing positive control.
  • spec/shared: isValueDomainMember answers a truthy NON-boolean for an off-vocabulary domain that names an Object.prototype member #15315 filed, not fixed — correct on both counts: unreachable from this call site (typed parameter, literal argument) and packages/spec is another lane's surface. ⭐ And the finding itself is sharp: a membership predicate that fails open, answering the truthy string '[object Object]' for domain: 'toString', with the existing pin unable to catch it because it iterates ValueDomainSchema.options — i.e. exactly the domains that behave.
  • The dedupe window stated as a limit ("600 most-recently-updated, not the whole backlog") rather than implied as exhaustive.

6. ⛔ What this is not

⛔ Not a contract review, because none is owed: Clause-② is no on both limbs, measured. This is the one delivery of the three in flight that does not wait on CONTRACT_REVIEW_TIER — that tier has been quota-exhausted in this session for ~2.5 hours (six readings, last 10:56Z), which is a fact about this caller and ⛔ never about the resource.

Flipping ready, then arming auto-merge — in that order, because auto-merge does not survive a draft conversion.


Generated by Claude Code

@zhuangjianguo
zhuangjianguo marked this pull request as ready for review September 4, 2026 10:58
@zhuangjianguo
zhuangjianguo added this pull request to the merge queue Sep 4, 2026
Merged via the queue into main with commit 6e67b86 Sep 4, 2026
35 checks passed
@zhuangjianguo
zhuangjianguo deleted the claude/issue-15134-core-timezone-predicate-repoint branch September 4, 2026 11:24
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 size/m tests tooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

finding(core): resolve-authz-context re-states the IANA time-zone probe that @objectstack/spec/shared now exports as the shared value-domain predicate

2 participants