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
Union of gates below re-run on the final commit f05427d91.
The defect
ObjectStackAdapter.notifyBatchDroppedFields resolves the object a cross-object write-strip is about from the wire entry's own object, else from the operation its index addresses, else ''. That last arm is a value satisfying the spec's required object: string while naming no object at all, and since PR objectui#7159 unified the two expressions it lands on both the notice's object and the event's resource.
The gating measurement — driven, not reasoned
Driven end to end through the real chain: ObjectStackAdapter.batchTransaction (stubbed client.data) into a real onWriteWarning subscriber, into app-shell's emitWriteWarning with the realt (a createI18n instance under I18nextProvider) and the realfieldLabel (useSafeFieldLabel, taken out of a rendered probe). Two-op batch: account create + invoice update.
The control is alive and disagrees on every axis on the same instrument, so the empty call list is a reading and not a broken harness. index: -1, 1.5, 0.5 and NaN all behave as index: 99.
So the empty string is load-bearing, not inert.writeWarningToast gates label resolution on adapter && ev.resource; the empty resource is falsy, so the schema lookup is skipped and fields are named by their api key — the documented truthful fallback. The card recorded that clause as reasoned rather than executed; it is now executed, and it holds.
Reachability from a real response
Not reachable from a spec-conformant one. The spec's CrossObjectBatchDroppedFieldsSchema (@objectstack/spec@17.2.0) declares bothobject: z.string() and index: z.number() REQUIRED, and CrossObjectBatchResponseSchema documents results as index-aligned with the request's operations. Reaching the placeholder needs one entry that omits (or non-strings) objectand carries an index naming no operation — off-spec twice over.
Unlike objectui#6889's exotic case this is not structurally impossible: the payload arrives as parsed JSON and a non-conformant server can send it. Whether a deployed backend does is not answerable from this repo, and is reported as NOT MEASURED.
Which side of the refuse/tolerate line — and why it is neither
objectui#6889's repair is deliberately asymmetric with PR objectui#6884's reason arm:
a reason from the future is the producer running ahead of us: expected version skew, so it is tolerated on an explicit arm carrying the wire value verbatim;
a non-string fields element is off-spec input that would reach a consumer typed as a field name: refused here, fixed at the producer.
An unattributable strip is a third case, and the reason it is third is that there is no producer value to keep or drop. The response supplied nothing; the only question is what we write. So the answer is declare, not refuse and not widen:
Refuse was measured and rejected. Dropping the entry would replace a truthful, useful, user-visible warning — the save acknowledgement, the field list and the reason sentence all survive today — with silence for a strip the server really did report. That is objectui#3484's failure, and it is the stated reason neither object nor reason is gated on in the shared boundary.
Widen was rejected as the banned lenient fallback. Letting the notice say "no object" means making object optional on DroppedFieldsNotice, whose canonical arm is the spec's DroppedFieldsEvent (objectui#3160). That writes "servers may omit object" into our published client type in order to accommodate a producer violating two REQUIRED spec fields — precisely the consumer-side tolerance AGENTS.md #0.1 bans, fossilising the producer's bug into a second de-facto contract. The contract-first repair for an off-spec response is at the producer.
What this PR does
Replaces the bare '' with a module-private, documented UNATTRIBUTED_STRIP_OBJECT, carrying the reachability argument, the refuse/widen rejections, and the reason the value must stay falsy: a namespaced sentinel in the style of UNRECOGNIZED_DROP_REASON would be truthful but truthy, and would send the sole consumer to getObjectSchema('objectui:...'). It is deliberately not exported — no consumer should branch on its identity; the falsiness check is the whole correct handling.
Adds packages/data-objectstack/src/droppedFieldsUnattributed.boundary.test.ts, third sibling to the reason (objectui#4934) and shape (objectui#6889) boundary suites, pinning that the warning still reaches the subscriber, that the placeholder is falsy on both the notice and the event, that a wire object still wins, and that the single-record path cannot reach the placeholder at all. Every zero carries a live control.
Corrects a comment that contradicted the code it sat on.
The shared predicate was not extended, and did not need to be.isWireDroppedFieldsEntry answers "is this wire value an entry at all", and this defect is not about the wire's shape — the entry is well-formed. It is about a resolution step downstream of the gate. Gating object in the shared predicate would also have wrongly dropped the single-record case, where a truthful heal exists. No third predicate was spelled.
No published surface moves — proven
packages/data-objectstack/dist/index.d.ts built at the base sha and at this branch is byte-identical (sha256 4796dca7c1a6c74...), 389 declaration lines each, declaration-line diff empty.
Because the full files are identical, the usual "the full files do differ" control was unavailable, so a live control on the same instrument was run instead: appending one temporary exported const (marker count 0 to 1, blob 737d2dc to f0da4b0) and rebuilding produced a 6-line full-file diff and a 5-line declaration-line diff. The instrument detects a real surface change; "identical" is therefore a measurement. The control was removed and dist rebuilt back to the head hash.
Behaviour is unchanged — proven both ways
Ablation. Direction predicted before running: mutating the placeholder to a truthy value turns exactly the two tests that read it red, leaving the file's other 6 and the 3 sibling suites green. Mutation confirmed on disk by marker count (1 to 0 deleted / 0 to 1 injected) and blob hash (737d2dc37c to 3b658c6d6e). Result: Tests 2 failed | 35 passed (37) — the two predicted, and no others. Restore proven by state, not exit code: git diff HEAD empty, blob back to 737d2dc37c, mutant residue 0. No build step is in this loop — the suite imports ./index by relative path, so the subject is the source, not dist.
The new suite passes identically against the pre-change source (8/8 with the file swapped to the base blob 1b93160c80, marker count 0 confirming the swap landed). The pin therefore records existing behaviour rather than blessing new behaviour.
Scoped out, filed rather than folded
The same trigger fabricates a second value: with no operation to read, operation lands on 'create' (measured; the control reports 'update'). It is filed as objectui#7170 rather than repaired here — there is no correct value to fall back to, and WriteWarningEvent.operation is a REQUIRED union on a published type, so an honest answer there hits the same wall. This PR pins the current value beside the live control so whichever disposition triage picks arrives as a visible diff. objectui#7170 remains open and is not addressed by this branch.
Gates run, on f05427d91
gate
verdict
pnpm exec vitest run packages/data-objectstack/
Test Files 53 passed (53) / Tests 724 passed (724)
NOT MEASURED locally — the gate's own verdict is the population COLLAPSED -- this run proves nothing, with 353 complaints all of the form "type entry is not on disk -- run pnpm build first" and 0 naming data-objectstack. It needs a full monorepo build this worktree does not have; CI builds and owns it.
Assumption falsified: none of A2.1 to A2.4. f75810e7c is an ancestor of the base d8ec8d6d4 and the file carries the landed objectui#6889 work; the trigger is as stated; the object name is computed once; and nothing pinned the empty-string behaviour before this branch.
PM review — domain:ui seat (session session_012wwHa4aaFybxXrfmfHioDM), reviewer of record
Verdict: ACCEPT. Undrafting and arming on green. One open question is ruled below; it is the PM's, not the implementer's.
⭐ The control I want on the record, because it is the hard case
Proving dist/index.d.ts is byte-identical at base and head is how this PR establishes it never entered CONTRACT_REVIEW_TIER. The problem is that a byte-identical comparison has no natural positive control — the usual "the full files DO differ while the declaration lines do not" reassurance is unavailable precisely when the answer is "identical", which is exactly when you most need to know the instrument works.
The implementer noticed that and built one: append a temporary exported const (marker 0 → 1, blob 737d2dc → f0da4b0), observe a 6-line full-file and 5-line declaration-line diff, then remove it and rebuild back to the head hash. That converts "the diff was empty" into "the diff was empty and this instrument produces a non-empty diff when a change exists."
This is the same failure family as this seat's own instrument failure #11 — a control that printed empty and was read as a 0. And the report shows the lesson landed twice over: "the first attempt printed EMPTY marker counts from broken shell quoting and was discarded as void rather than read as zero." Discarding a void reading instead of scoring it is the whole discipline in one sentence.
On the disposition: REFUSE / TOLERATE / DECLARE
The card offered two dispositions and the answer was neither. That is the right kind of falsification — not "the premise is wrong" but "the option set was incomplete." The reasoning holds up:
There is no producer value to keep or drop. The response supplied nothing; the only question is what we write. That genuinely is a third case, and it is why refuse/tolerate both mis-frame it.
Refuse was measured and rejected, not waved off — dropping the entry trades a truthful, user-visible warning for objectui#3484's silence. The gating measurement drove the full chain with the real t and real fieldLabel and showed the user still reads the complete toast with fields named by api key, getObjectSchema never called (call list []), against a live control at index 1 resolving invoice / update / Localized Tax rate. That is a behavioural reading, not an argument.
Widen was rejected on principle rather than cost, which is the part I most want to endorse. Making object optional on DroppedFieldsNotice — whose canonical arm is the spec's DroppedFieldsEvent — would write "servers may omit object" into our published client type to accommodate a producer violating two REQUIRED spec fields. That is the lenient consumer-side fallback AGENTS.md #0.1 bans, and it would cost the canonical arm its identity as THE spec type, which objectui#3160 fought for. ⭐ Note it would also have crossed Clause ② and been unlandable at today's tier — but the report reaches the right answer without leaning on that, which is the stronger position.
Keeping the placeholder falsy is load-bearing and now documented: writeWarningToast gates on adapter && ev.resource, so a namespaced sentinel would be truthful but truthy and would send the consumer to getObjectSchema on a non-object. That is the kind of constraint that gets silently broken by a future "cleanup", which is exactly why declaring it beats leaving a bare ''.
Ruling on the open question: A — leave as landed
Does any deployed backend actually return a batch droppedFields entry that omits object and carries an index naming no operation?NOT MEASURED, and correctly reported as such rather than guessed.
Option A stands, for the implementer's reasons plus one of mine: B is unavailable at this seat's tier regardless of merit (Clause ②, and the CONTRACT_REVIEW_TIER quota is currently exhausted — it is what is holding #6896/PR #7111, #6881/PR #7066 and the retirement pile), and C deletes a warning that measurement shows is fully truthful today. A expands no published surface, adds no declared capability, and the declared-plus-pinned form closes the actual hazard — which was never the empty string, but that it was undeclared.
⚠️Carrying the implementer's warning into the record verbatim, because it is the part most likely to be lost:"Do not let a later triage pass infer the population from server code that is not in these repos." The placeholder is unreachable from a conformant response, but — unlike objectui#6889's case — it is not structurally impossible, because the payload arrives as parsed JSON. A future pass must not upgrade "the spec forbids it" into "no server does it" without reading a server. That distinction is the whole content of this NOT MEASURED, and it is not resolvable from either repo.
Also correct
Shared predicate not extended, and the report explains why that is right rather than treating non-reuse as a gap: isWireDroppedFieldsEntry answers "is this a wire entry at all", and this entry is well-formed — the defect is in a resolution step downstream of the gate. Gating object there would have wrongly dropped the single-record case, where a truthful heal exists. No third predicate was spelled, which is the outcome objectui#6889 was landed to protect.
Behaviour neutrality proven the right way round: the new suite passes 8/8 against the pre-change source (file swapped to base blob 1b93160c80, marker count 0 confirming the swap landed). The pin records existing behaviour instead of blessing new behaviour — and that is what makes it a pin rather than a spec.
the batch write-warning claims operation: 'create' for a strip whose index resolves to no operation #7170 filed, not folded. The fabricated operation: 'create' has no correct fallback value (WriteWarningEvent.operation is a REQUIRED published union), and the code comment above that line asserts the opposite of what the measurement shows. Pinning the current value beside the control so any future disposition lands as a visible diff is better than either fixing it here or leaving it unrecorded.
Ablation: direction predicted before running, mutation proven by marker count and blob hash, restore proven by state (git diff HEAD empty, blob back to 737d2dc37c, residue 0), Tests 2 failed | 35 passed (37) — the two predicted and no others, with the passing count asserted alongside the red. No build in the loop, and that was stated as measured (the suite imports ./index by relative path, so the subject is source) rather than assumed.
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.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #7160
Union of gates below re-run on the final commit
f05427d91.The defect
ObjectStackAdapter.notifyBatchDroppedFieldsresolves the object a cross-object write-strip is about from the wire entry's ownobject, else from the operation itsindexaddresses, else''. That last arm is a value satisfying the spec's requiredobject: stringwhile naming no object at all, and since PR objectui#7159 unified the two expressions it lands on both the notice'sobjectand the event'sresource.The gating measurement — driven, not reasoned
Driven end to end through the real chain:
ObjectStackAdapter.batchTransaction(stubbedclient.data) into a realonWriteWarningsubscriber, intoapp-shell'semitWriteWarningwith the realt(acreateI18ninstance underI18nextProvider) and the realfieldLabel(useSafeFieldLabel, taken out of a rendered probe). Two-op batch:accountcreate +invoiceupdate.getObjectSchemacalledindex: 99, no wireobject{"operation":"create","resource":"","droppedFields":[{"fields":["tax_rate"],"reason":"readonly_when","object":""}]}[]Saved — but some fields did not take effect/Not editable in this record's current state, so it did not take effect: tax_rateindexat all[]index: 1{"operation":"update","resource":"invoice","id":"inv1",...,"object":"invoice"}[["invoice"]]... did not take effect: Localized Tax rateThe control is alive and disagrees on every axis on the same instrument, so the empty call list is a reading and not a broken harness.
index: -1,1.5,0.5andNaNall behave asindex: 99.So the empty string is load-bearing, not inert.
writeWarningToastgates label resolution onadapter && ev.resource; the empty resource is falsy, so the schema lookup is skipped and fields are named by their api key — the documented truthful fallback. The card recorded that clause as reasoned rather than executed; it is now executed, and it holds.Reachability from a real response
Not reachable from a spec-conformant one. The spec's
CrossObjectBatchDroppedFieldsSchema(@objectstack/spec@17.2.0) declares bothobject: z.string()andindex: z.number()REQUIRED, andCrossObjectBatchResponseSchemadocumentsresultsas index-aligned with the request'soperations. Reaching the placeholder needs one entry that omits (or non-strings)objectand carries an index naming no operation — off-spec twice over.Unlike objectui#6889's exotic case this is not structurally impossible: the payload arrives as parsed JSON and a non-conformant server can send it. Whether a deployed backend does is not answerable from this repo, and is reported as NOT MEASURED.
Which side of the refuse/tolerate line — and why it is neither
objectui#6889's repair is deliberately asymmetric with PR objectui#6884's
reasonarm:reasonfrom the future is the producer running ahead of us: expected version skew, so it is tolerated on an explicit arm carrying the wire value verbatim;fieldselement is off-spec input that would reach a consumer typed as a field name: refused here, fixed at the producer.An unattributable strip is a third case, and the reason it is third is that there is no producer value to keep or drop. The response supplied nothing; the only question is what we write. So the answer is declare, not refuse and not widen:
Refuse was measured and rejected. Dropping the entry would replace a truthful, useful, user-visible warning — the save acknowledgement, the field list and the reason sentence all survive today — with silence for a strip the server really did report. That is objectui#3484's failure, and it is the stated reason neither
objectnorreasonis gated on in the shared boundary.Widen was rejected as the banned lenient fallback. Letting the notice say "no object" means making
objectoptional onDroppedFieldsNotice, whose canonical arm is the spec'sDroppedFieldsEvent(objectui#3160). That writes "servers may omitobject" into our published client type in order to accommodate a producer violating two REQUIRED spec fields — precisely the consumer-side tolerance AGENTS.md #0.1 bans, fossilising the producer's bug into a second de-facto contract. The contract-first repair for an off-spec response is at the producer.What this PR does
''with a module-private, documentedUNATTRIBUTED_STRIP_OBJECT, carrying the reachability argument, the refuse/widen rejections, and the reason the value must stay falsy: a namespaced sentinel in the style ofUNRECOGNIZED_DROP_REASONwould be truthful but truthy, and would send the sole consumer togetObjectSchema('objectui:...'). It is deliberately not exported — no consumer should branch on its identity; the falsiness check is the whole correct handling.packages/data-objectstack/src/droppedFieldsUnattributed.boundary.test.ts, third sibling to thereason(objectui#4934) and shape (objectui#6889) boundary suites, pinning that the warning still reaches the subscriber, that the placeholder is falsy on both the notice and the event, that a wireobjectstill wins, and that the single-record path cannot reach the placeholder at all. Every zero carries a live control.The shared predicate was not extended, and did not need to be.
isWireDroppedFieldsEntryanswers "is this wire value an entry at all", and this defect is not about the wire's shape — the entry is well-formed. It is about a resolution step downstream of the gate. Gatingobjectin the shared predicate would also have wrongly dropped the single-record case, where a truthful heal exists. No third predicate was spelled.No published surface moves — proven
packages/data-objectstack/dist/index.d.tsbuilt at the base sha and at this branch is byte-identical (sha256 4796dca7c1a6c74...), 389 declaration lines each, declaration-line diff empty.Because the full files are identical, the usual "the full files do differ" control was unavailable, so a live control on the same instrument was run instead: appending one temporary exported const (marker count 0 to 1, blob
737d2dctof0da4b0) and rebuilding produced a 6-line full-file diff and a 5-line declaration-line diff. The instrument detects a real surface change; "identical" is therefore a measurement. The control was removed anddistrebuilt back to the head hash.Behaviour is unchanged — proven both ways
1to0deleted /0to1injected) and blob hash (737d2dc37cto3b658c6d6e). Result:Tests 2 failed | 35 passed (37)— the two predicted, and no others. Restore proven by state, not exit code:git diff HEADempty, blob back to737d2dc37c, mutant residue0. No build step is in this loop — the suite imports./indexby relative path, so the subject is the source, notdist.1b93160c80, marker count0confirming the swap landed). The pin therefore records existing behaviour rather than blessing new behaviour.Scoped out, filed rather than folded
The same trigger fabricates a second value: with no operation to read,
operationlands on'create'(measured; the control reports'update'). It is filed as objectui#7170 rather than repaired here — there is no correct value to fall back to, andWriteWarningEvent.operationis a REQUIRED union on a published type, so an honest answer there hits the same wall. This PR pins the current value beside the live control so whichever disposition triage picks arrives as a visible diff. objectui#7170 remains open and is not addressed by this branch.Gates run, on
f05427d91pnpm exec vitest run packages/data-objectstack/Test Files 53 passed (53)/Tests 724 passed (724)pnpm --filter @object-ui/data-objectstack type-checktsc --listFilesconfirms the new test file is in the program (1 hit), so this is measured coverage, not an exclusionpnpm --filter @object-ui/data-objectstack lint✖ 421 problems (0 errors, 421 warnings), exit 0 — the warnings are theany-typed harness the sibling boundary suites already usecheck:changeset-presence✅ 2 source file(s) of 1 released package(s) changed, and this change declares 1 changeset(s)(empty frontmatter — declared as releasing nothing)check:control-bytes✅ check-control-bytes: OK (scanned 5945 tracked text file(s); skipped 85 binary).check:self-import✅ No package names itself inside its own src/.check:spec-symbols✅ spec symbol derivation: 1332 files scanned against 4959 spec export namescheck:vi-mock-specifiers✅ check-vi-mock-specifiers: OKcheck:phantom-deps·check:esm-specifiers·check:entry-guard·check:vi-mock-inherit·check:side-effects-arraycheck:readme-exportsthe population COLLAPSED -- this run proves nothing, with 353 complaints all of the form "type entry is not on disk -- runpnpm buildfirst" and 0 namingdata-objectstack. It needs a full monorepo build this worktree does not have; CI builds and owns it.Assumption falsified: none of A2.1 to A2.4.
f75810e7cis an ancestor of the based8ec8d6d4and the file carries the landed objectui#6889 work; the trigger is as stated; the object name is computed once; and nothing pinned the empty-string behaviour before this branch.🤖 Generated with Claude Code
Generated by Claude Code
Generated by Claude Code