Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions .changeset/lint-provenance-anchors.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
---
'@objectstack/lint': patch
---

Provenance comments in `@objectstack/lint` were re-anchored

Comment and docblock lines under `src/` that cited tracker numbers which no
longer resolve on GitHub now cite the commit in this repository's history that
decided the matter, and say in their own words what was decided. Comments
only: no rule id, finding message, hint, severity, type or runtime behaviour
changes.
2 changes: 1 addition & 1 deletion packages/lint/src/data-model-rules.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ export interface LintIssue {
* runtime gate's `fingerprint` reads `where` and `path` together (making
* `where` more specific cannot merge two findings that were distinct).
*
* [#10064] On the runtime gate's WIRE surface (`RuntimeAuthoringIssue.path`,
* [commit def0d3e63] On the runtime gate's WIRE surface (`RuntimeAuthoringIssue.path`,
* the 422 `issues[]` / 2xx `advisories`), the top-level collection index of a
* collection-resident finding is rewritten to the entry's NAME
* (`objects[417].sharingModel` → `objects.acme_invoice.sharingModel`) after
Expand Down
4 changes: 2 additions & 2 deletions packages/lint/src/flow-template-grammar.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@
* - `NOW()` / `TODAY()` with an optional `± N` day offset — closed, two names.
* - `$User.<path>` — closed prefix.
* - `round` / `floor` / `ceil` / `abs` / `min` / `max` in CALL position —
* closed by maintainer ruling on #11060 ("exactly … every name and semantic
* closed by the maintainer ruling commit 815585513 records ("exactly … every name and semantic
* mirrored **1:1 from the CEL stdlib**, ⛔ no second semantics invented").
* - a bare or dotted identifier (`{recordId}`, `{record.id}`, `{status}`) —
* **OPEN**: it addresses the run's `VariableMap`, which holds the flow's
Expand Down Expand Up @@ -101,7 +101,7 @@ export const FLOW_TEMPLATE_DATE_FUNCTIONS: readonly string[] = ['NOW', 'TODAY'];

/**
* The value-expression function table — the CEL stdlib's numeric six, by the
* #11060 ruling. Mirrors `EXPRESSION_FUNCTION_ARITY`'s key set.
* ruling commit 815585513 records. Mirrors `EXPRESSION_FUNCTION_ARITY`'s key set.
*/
export const FLOW_TEMPLATE_VALUE_FUNCTIONS: readonly string[] = [
'round', 'floor', 'ceil', 'abs', 'min', 'max',
Expand Down
18 changes: 9 additions & 9 deletions packages/lint/src/lint-flow-patterns.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@
* specifically (range operators `>=`/`<=` are not flagged — they're the building
* block of the correct pattern), keeping false positives near zero.
*
* #13681 / #14394 — per-iteration containment, as a PAIR of rules. A `loop`
* commit 8ed9c54b4 / #14394 — per-iteration containment, as a PAIR of rules. A `loop`
* body has no error handling of its own: `loop-node.ts` iterates with a bare
* `await`, so the first item whose node fails ends the entire run and every
* later item goes unprocessed, silently. The containment spelling exists and was
Expand Down Expand Up @@ -261,7 +261,7 @@ export const FLOW_DECISION_INCLUSIVE_OVERLAP = 'flow-decision-inclusive-overlap'
*/
export const FLOW_MULTI_WRITE_UNFILTERED = 'flow-multi-write-unfiltered';
/**
* #13681 / #14394 — a `loop` body that runs a node which can fail, with no
* commit 8ed9c54b4 / #14394 — a `loop` body that runs a node which can fail, with no
* `try_catch` between the loop and that node. The first failing item kills the
* whole sweep: `loop-node.ts:123-135` iterates with a bare `await` and no
* `try`/`catch` anywhere in the file, so the body's failure propagates out of
Expand All @@ -279,7 +279,7 @@ export const FLOW_MULTI_WRITE_UNFILTERED = 'flow-multi-write-unfiltered';
*/
export const FLOW_LOOP_BODY_UNCONTAINED = 'flow-loop-body-uncontained';
/**
* #13681 / #14394 — the near-miss shape: a `try_catch` that declares no `catch`
* commit 8ed9c54b4 / #14394 — the near-miss shape: a `try_catch` that declares no `catch`
* region. `catch` is optional in the schema (`control-flow.zod.ts:315`) and
* omitting it makes the container **fail** (`try-catch-node.ts:190`), so the
* wrapped region dies exactly like an unwrapped one — measured side by side, the
Expand Down Expand Up @@ -340,7 +340,7 @@ const DATA_NODE_TYPES = new Set(['get_record', 'create_record', 'update_record',
* - `http` — a non-2xx (when `failOnError`), a timeout/abort, or a failed
* durable enqueue (`http-nodes.ts:208, :249-253`).
* - `notify` — no title, an empty resolved recipient set, or a delivery throw
* (`notify-node.ts:293, :300, :446`). The measured #13681 case exactly: one
* (`notify-node.ts:293, :300, :446`). The measured case commit 8ed9c54b4 records, exactly: one
* row with a null owner killed the sweep.
* - `connector_action` — a degraded connector, an unresolvable action, or a
* throwing call (`connector-nodes.ts:69, :76, :124`).
Expand Down Expand Up @@ -1330,7 +1330,7 @@ function scanApprovalReviseLoops(

/**
* The minimal `catch` region, measured end to end on the real `AutomationEngine`
* (#13681) and quoted verbatim by both containment rules and by
* (commit 8ed9c54b4) and quoted verbatim by both containment rules and by
* `content/docs/automation/flows.mdx`.
*
* `catch` cannot be empty: `FlowRegionSchema.nodes` is `.min(1)`
Expand Down Expand Up @@ -1425,7 +1425,7 @@ function scanUncontainedLoopBodies(
`that is a legitimate reading and this stays a warning. ` +
// The tracker ids stay OUT of the runtime string (`check:doc-authoring`):
// an author reading this hint cannot resolve `#NNNN`. The measurement
// and the ruling behind this rule are #13681 / #14394; the docblock on
// and the ruling behind this rule are commit 8ed9c54b4 / #14394; the docblock on
// {@link FLOW_LOOP_BODY_UNCONTAINED} carries them for the reader who can.
`See content/docs/automation/flows.mdx §"Per-iteration containment".`,
// Warning, not `error`: see the severity policy at the top of this
Expand Down Expand Up @@ -1465,7 +1465,7 @@ function scanUncontainedLoopBodies(
/**
* #14394 rule B — a `try_catch` with no `catch` region, anywhere in the flow.
*
* Measured (#13681): the container fails through, and the run is byte-identical
* Measured (commit 8ed9c54b4): the container fails through, and the run is byte-identical
* to the one with no `try_catch` at all. `retry`, when present, only delays it.
*
* A `catch` that is PRESENT but malformed is deliberately not this rule's
Expand Down Expand Up @@ -1737,15 +1737,15 @@ export function lintFlowPatterns(stack: AnyRec): FlowLintFinding[] {
// purge, and this rule's main habitat — in range (#5383/#5635).
scanUnboundedBulkWrites(at, graphNodes, findings);

// (g) #13681/#14394 — a `loop` body running a fallible node with no
// (g) commit 8ed9c54b4 / #14394 — a `loop` body running a fallible node with no
// `try_catch` between the loop and it. Per graph like the rest, and
// that is what keeps the count right: every `loop` node belongs to
// exactly one graph, so its body is descended exactly once, and a
// nested loop is judged in its own graph rather than through its
// parent (see {@link scanUncontainedLoopBodies}).
scanUncontainedLoopBodies(at, graphNodes, findings);

// (h) #13681/#14394 — the near-miss: a `try_catch` with no `catch`. Scanned
// (h) commit 8ed9c54b4 / #14394 — the near-miss: a `try_catch` with no `catch`. Scanned
// everywhere, not only inside a loop: the container fails through
// wherever it is written. Inside a loop body it is the shape (g)
// deliberately treats as contained, so exactly one of the two rules
Expand Down
6 changes: 3 additions & 3 deletions packages/lint/src/lint-liveness-properties.ts
Original file line number Diff line number Diff line change
Expand Up @@ -273,7 +273,7 @@ function describe(entry: LedgerEntry): { kind: string; rule: string; defaultHint

/**
* ── Coverage seam (#14057). Package-internal: NOT part of the published surface,
* same posture as the #10262 seam below `getNested` (exported from the MODULE
* same posture as the test seam commit 2aca1bc4c added below `getNested` (exported from the MODULE
* only; `src/index.ts` re-exports neither, and this package's `exports` map
* publishes just `.` and `./runtime`). ────────────────────────────────────
*
Expand Down Expand Up @@ -431,7 +431,7 @@ export function getNested(obj: AnyRec, path: string): unknown[] {
}

/**
* ── Test seam (#10262). Package-internal: NOT part of the published surface ──
* ── Test seam (commit 2aca1bc4c). Package-internal: NOT part of the published surface ──
*
* `getNested` above and this wrapper are exported for
* `lint-liveness-properties.test.ts` to drive the array fan-out against a
Expand All @@ -455,7 +455,7 @@ export function getNested(obj: AnyRec, path: string): unknown[] {
* - #7079 was closed by re-subjecting to `app.…navigation.children.runAction`;
* - #10068 flipped THAT live → subject lost again, and measured across all 30
* shipped ledgers every remaining warned entry is top-level, so there is
* nothing left to re-subject to. Filed as #10262 (this seam).
* nothing left to re-subject to. This seam is commit 2aca1bc4c.
*
* A broken walk is invisible without it: a `getNested` that stopped at index 0
* "still warns on every single-entry fixture, on every top-level warned key,
Expand Down
2 changes: 1 addition & 1 deletion packages/lint/src/reference-integrity-suite.ts
Original file line number Diff line number Diff line change
Expand Up @@ -563,7 +563,7 @@ export const REFERENCE_INTEGRITY_RULES: readonly ReferenceIntegrityRule[] = [
// build the other command would have stopped. Joining the suite is the whole
// fix; the two hand-wired call sites are deleted with it (#4345 follow-up).
{ name: 'validateReadonlyFlowWrites', run: validateReadonlyFlowWrites },
// [#13653] The SAME question as the member above, on the surface that had no
// [commit 36d287803] The SAME question as the member above, on the surface that had no
// answer for it: a hook body's `ctx.api.object('x').update({ readonlyField })`.
// A hook's `ctx.api` is a ScopedContext over the TRIGGERING operation's
// context, so on a non-system trigger the engine strips the key and the call
Expand Down
18 changes: 9 additions & 9 deletions packages/lint/src/runtime-gate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -170,7 +170,7 @@ const TYPE_TO_STACK_KEY: Readonly<Record<string, string>> = {
report: 'reports',
email_template: 'emailTemplates',
mapping: 'mappings',
// [#19143 above, #19370 here] `position` / `app` — the two collections only
// [#19143 above, commit a227afa41 here] `position` / `app` — the two collections only
// `security-role-word` judges. They arrive together with that rule's
// crossing, never ahead of it: `DEFAULT_METADATA_TYPE_REGISTRY` declares both
// `allowRuntimeCreate: true`, so Studio's app designer, REST `/meta` and an
Expand Down Expand Up @@ -261,7 +261,7 @@ const TYPE_TO_STACK_KEY: Readonly<Record<string, string>> = {
* here plus a `CONTEXT_STACK_KEYS` entry, made when a rule that RESOLVES
* REFERENCES INTO the collection actually crosses the wall, never in advance.
*
* [#19370] `positions` / `apps` are the measured limit of that sentence, and
* [commit a227afa41] `positions` / `apps` are the measured limit of that sentence, and
* the reason it now says RESOLVES rather than reads. `security-role-word`
* crossed the wall reading both collections, and they are still not carried:
* the rule judges each identifier and label on its own, so the universe it
Expand Down Expand Up @@ -359,7 +359,7 @@ export interface RuntimeStackContext {
* read — narrows NOTHING. The whole collection is handed over, exactly as
* before. The fallback direction is deliberate and is the opposite of a size
* threshold: an unknown provenance buys MORE validation input, never less, so
* the gate never stops judging (the #9798 / #9261 / ADR-0110 D3 fail-open
* the gate never stops judging (the fail-open commit c7655d472 closed, #9261, ADR-0110 D3: the
* shape this card was explicitly forbidden from re-creating).
*/
export interface RuntimePackageScope {
Expand Down Expand Up @@ -724,7 +724,7 @@ export const WRITTEN_STACK_KEYS: ReadonlySet<string> = new Set(Object.values(TYP

/**
* The collection-resident stack keys whose TOP-LEVEL index the gate rewrites
* to a name key before findings leave it (#10064) — DERIVED, not listed (#13390).
* to a name key before findings leave it (commit def0d3e63) — DERIVED, not listed (#13390).
*
* These are the collections a written item lands INSIDE **and** that the
* context also fills — so a finding's `objects[417]` is an offset into this
Expand All @@ -743,7 +743,7 @@ export const WRITTEN_STACK_KEYS: ReadonlySet<string> = new Set(Object.values(TYP
* clause, which is validity, not completeness, and the compiler held nothing
* else. Omitting a member here did not fail to build, fail a test, or fail a
* gate; it emitted findings that LOOK correct whose `path` the caller cannot
* resolve, which is the #10064 defect re-created silently.
* resolve, which is the defect commit def0d3e63 fixed, re-created silently.
*
* [#13977] That reading of `CONTEXT_STACK_KEYS` is now history rather than
* description: it is derived from `RuntimeStackContext` and complete by
Expand Down Expand Up @@ -776,8 +776,8 @@ export const WRITTEN_STACK_KEYS: ReadonlySet<string> = new Set(Object.values(TYP
* had to hand-write. Mapping the `dataset` type moved it IN, in the same one-key
* edit and with no second spelling to remember: a dataset write's snapshot now
* holds the tenant's other datasets beside the written one, so `datasets[3]` is
* again an offset into an array the caller has never seen. That is the #10064
* defect this constant exists to prevent, and the derivation caught the widening
* again an offset into an array the caller has never seen. That is the defect commit def0d3e63 fixed,
* the one this constant exists to prevent, and the derivation caught the widening
* rather than being told about it.
*/
const NAME_KEYED_STACK_KEYS: readonly string[] = deriveNameKeyedStackKeys(
Expand All @@ -796,7 +796,7 @@ const PATH_SAFE_NAME = /^[A-Za-z_][A-Za-z0-9_]*$/;
const TOP_LEVEL_INDEX = buildTopLevelIndexPattern(NAME_KEYED_STACK_KEYS);

/**
* `objects[417].sharingModel` → `objects.acme_invoice.sharingModel` (#10064).
* `objects[417].sharingModel` → `objects.acme_invoice.sharingModel` (commit def0d3e63).
*
* The maintainer-ruled wire shape for collection-resident findings: the
* top-level collection index no caller can resolve is replaced by the entry's
Expand Down Expand Up @@ -925,7 +925,7 @@ export function runRuntimeAuthoringRules(args: {
const before = new Set(runRules(rules, snapshots.baseline, ctx).map(fingerprint));
const added = runRules(rules, snapshots.candidate, ctx)
.filter((f) => !before.has(fingerprint(f)))
// [#10064] The wire shape: collection-resident findings key their
// [commit def0d3e63] The wire shape: collection-resident findings key their
// top-level collection entry by NAME, not by this gate's private snapshot
// index. Rewritten only on what leaves the gate — the differential above
// ran on the rules' raw positional paths.
Expand Down
6 changes: 3 additions & 3 deletions packages/lint/src/validate-action-body-writes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,7 @@ export const ACTION_RECORD_WRITE_DISCARDED = 'action-record-write-discarded';
export const ACTION_BODY_SOURCE_UNPARSEABLE = 'action-body-source-unparseable';

/**
* [#8663] The action-surface twin of `hook-body-write-unprovisioned-anchor`.
* [commit 192213f66] The action-surface twin of `hook-body-write-unprovisioned-anchor`.
* Same question, same wording, same `warning` severity — this rule and the hook
* rule share {@link IMPLICIT_FIELDS}, so they shared its blind spot too.
*/
Expand Down Expand Up @@ -314,7 +314,7 @@ export function validateActionBodyWrites(stack: AnyRec): ActionBodyWriteFinding[
// Built lazily: only the unknown-field check needs it, so a stack whose
// action bodies never reach `ctx.api` never pays it.
let objectFields: Map<string, Set<string>> | null = null;
// [#8663] Non-empty only for a stack carrying an ADR-0015 `external` object.
// [commit 192213f66] Non-empty only for a stack carrying an ADR-0015 `external` object.
let anchors: ReadonlyMap<string, ReadonlySet<string>> | null = null;

for (const site of sites) {
Expand Down Expand Up @@ -399,7 +399,7 @@ export function validateActionBodyWrites(stack: AnyRec): ActionBodyWriteFinding[
// (it maps a remote column they vouch for) — never either finding.
if (known.has(w.field)) continue;
if (IMPLICIT_FIELDS.has(w.field)) {
// [#8663] Implicitly writable SOMEWHERE is not provisioned HERE.
// [commit 192213f66] Implicitly writable SOMEWHERE is not provisioned HERE.
if (!anchors.get(w.object)?.has(w.field)) continue;
reported.add(dedupeKey);
findings.push({
Expand Down
2 changes: 1 addition & 1 deletion packages/lint/src/validate-component-types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

/**
* [ADR-0078] The page-component TYPE gate — the author-time rejection the open
* `type` union never had (#12950, riding the #12183 ruling of 2026-08-26).
* `type` union never had (commit 225e7690f, riding the #12183 ruling of 2026-08-26).
*
* ## What was missing
*
Expand Down
2 changes: 1 addition & 1 deletion packages/lint/src/validate-empty-combinators.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
* ## The runtime is not changing, and this file changes nothing about it
*
* #5322 (maintainer ruling, 2026-08-04) settled the RUNTIME semantics of the
* four empty shapes as the boolean identity reduction, and #5659/PR #6528 made
* four empty shapes as the boolean identity reduction, and #5659 (commit 3510e4a25) made
* that reduction one implementation — `reduceFilterVerdict` in
* `@objectstack/spec/data`, proven against `FILTER_LOGIC_CASES` and consumed by
* every backend. This rule touches no translate or evaluation path. It asks the
Expand Down
10 changes: 5 additions & 5 deletions packages/lint/src/validate-expressions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -525,7 +525,7 @@ function rulePredicates(rule: AnyRec, path: string): Array<{ label: string; raw:
*
* ## Why this is a rule of its own rather than a missing root
*
* Until #6290 the same rejection fell out of `@objectstack/formula`'s
* Until commit e9b526597 the same rejection fell out of `@objectstack/formula`'s
* `SCOPE_ROOTS` not listing `current_user` — a global baseline, doing a
* per-surface job by accident. Two things were wrong with that:
*
Expand Down Expand Up @@ -568,7 +568,7 @@ function rulePredicates(rule: AnyRec, path: string): Array<{ label: string; raw:
*
* A denylist cannot track `SCOPE_ROOTS`: every root added there is unreported
* here until somebody remembers to copy it across (`current_user` itself
* arrived in #6290 and needed #6584 to be noticed). The allowlist inverts the
* arrived in commit e9b526597, and needed that same change (#6584) to be noticed). The allowlist inverts the
* maintenance burden onto the three roots that are pinned by three anchors and
* change only when the evaluators do.
*
Expand Down Expand Up @@ -767,7 +767,7 @@ function rulePredicates(rule: AnyRec, path: string): Array<{ label: string; raw:
*
* `SCOPE_ROOTS`' own docblock made the original widening measurable rather than
* a matter of taste: its `current_user` entry claims to be "the last one this
* list was missing (#6290)". `app` is that sentence's second counterexample —
* list was missing" (commit e9b526597 wrote it). `app` is that sentence's second counterexample —
* the same mechanism (#6713's point: a hand-maintained list doing a per-surface
* job drifts), a second sighting, not an analogy to the first.
*
Expand Down Expand Up @@ -1890,7 +1890,7 @@ export function validateStackExpressions(stack: AnyRec): ExprIssue[] {
issues.push({ where, message: verdict.message, source: verdict.source, severity: 'error' });
}
}
// [#6290] Per-OPTION `visibleWhen` — a `select`/`multiselect`/`radio`
// [commit e9b526597] Per-OPTION `visibleWhen` — a `select`/`multiselect`/`radio`
// option's own predicate (`SelectOptionSchema.visibleWhen`,
// `field.zod.ts:143`). It had no traversal here at all, so the whole
// option surface reached compile, validate and run time unvalidated:
Expand All @@ -1900,7 +1900,7 @@ export function validateStackExpressions(stack: AnyRec): ExprIssue[] {
//
// Deliberately checked on the SAME `record` scope as the field-level
// slots, and that is the whole of the difference between the two faces
// after #6290: `current_user` is a declared root platform-wide (ADR-0068
// after commit e9b526597: `current_user` is a declared root platform-wide (ADR-0068
// D1), so it passes here — options resolve through
// `resolveCascadingOptions` against the host's predicate scope, which
// binds it (ADR-0068 / objectui#2284), and the showcase's
Expand Down
Loading
Loading