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
`evaluateCondition` now refuses a malformed condition shape with the same `STRUCTURAL_CONDITION_SHAPE_REFUSAL` registration already raises — evaluation and registration share one refusal, so a shape that slipped past registration can never surface as a raw `TypeError` or as a silent `false`.
6
+
7
+
#15662 closed the reject set at the producer: `registerFlow` refuses a structural condition (`config.condition` on a node, `edge.condition`) that is neither CEL text nor an expression envelope. The evaluator was left saying the opposite thing in a different vocabulary, and that half matters because `evaluateCondition` is a **public method on an exported class** — a plugin reaches it directly regardless of what `registerFlow` admits, and a flow stored before that gate landed replays through it.
8
+
9
+
The unguarded read had three arms, all of them now refused by the shared `structuralConditionRefusal` — the same call `registerFlow` makes, not a second hand-written envelope that could drift from it:
10
+
11
+
- an envelope whose `source` is present and **not a string** (`{ source: 1 }`, `{ dialect: 'cel', source: 1 }`) reached `.trim()` and threw `TypeError: exprStr.trim is not a function`, naming no flow, no node and no expression;
12
+
- a value that is neither text nor envelope-shaped (`42`, `true`, `['a']`, `{}`, `{ dialect: 'cel' }`) was read as an **empty condition** and answered `false` — the "an unauthored branch must not open" rule applied to a value that was very much authored, on the same key a start node's **trigger gate** is read from;
13
+
- a malformed envelope carrying a non-predicate dialect (`{ dialect: 'cron', source: 1 }`) answered `false` one statement earlier still, at the dialect check, never reaching the source derivation at all.
14
+
15
+
**What still evaluates is unchanged, and is pinned as controls.** Bare CEL text and both envelope spellings evaluate exactly as before; an `ast`-only envelope still answers `false`; a well-formed non-predicate dialect (`{ dialect: 'cron', source: '0 0 * * *' }`) still answers `false` rather than being refused; absent, `null`, empty and whitespace-only conditions are still "not authored", not malformed. A malformed **string** still earns its own verdict — the brace trap or the ADR-0032 §1c CEL fault — never the shape refusal.
16
+
17
+
An app whose stored flow carries one of the refused shapes in a node or edge `condition` now fails that run loudly with a message carrying the rule, instead of skipping a branch in silence or faulting unattributed; the fix is to write the condition as bare CEL text (`record.rating >= 4`) or as an expression envelope.
`nav-object-ungranted`'s hint no longer tells you to gate the nav entry with `requiredPermissions`/`visible` — that never cleared the finding, because the rule never reads either key. Gating restricts who can see the entry; it doesn't grant the object read, so a holder who clears the gate could still hit permission-denied, and the warning kept firing anyway. The hint (and the module doc-block) now name the two remedies that actually clear it: grant read on the object in a permission set (`allowRead: true` or `viewAllRecords`), or drop the nav entry. No behavior change — the rule fires and stays silent on exactly the same inputs as before; only the wording of the hint moved.
feat(cli): point `@objectstack/cli/console` at a public barrel with a name-and-shape pin
6
+
7
+
**BREAKING**: `@objectstack/cli/console` publishes three names instead of thirteen. Ten names it used to resolve no longer resolve through that subpath.
8
+
9
+
The subpath pointed straight at `dist/utils/console.js` — an internal module — and carried no surface pin of any kind, neither names nor shapes. Two assertions did exist and neither is one: `./console` was held among the declared `exports` keys, and the specifier was held to resolving from the packed tarball. Both answer *is the door open*; neither can answer *what is behind it*. So all thirteen of that module's top-level exports were public API, and every export it gained afterwards became a permanent public contract the moment it landed, silently.
10
+
11
+
The subpath stays open and now points at a dedicated barrel, `dist/console.js`, which re-exports by name (no star) exactly the three helpers the one ledgered out-of-repo consumer uses to mount the Console SPA:
12
+
13
+
-`resolveConsolePath`
14
+
-`hasConsoleDist`
15
+
-`createConsoleStaticPlugin`
16
+
17
+
Those three keep their existing shapes exactly, so a consumer importing only them compiles unchanged.
18
+
19
+
These ten are no longer reachable through `@objectstack/cli/console`:
20
+
21
+
-`CONSOLE_PATH`
22
+
-`ConsoleShaDrift`
23
+
-`DRIFT_OVERRIDE_ENV`
24
+
-`ResolveConsoleOptions`
25
+
-`createRuntimeAssetsPlugin`
26
+
-`decideConsoleMount`
27
+
-`detectConsoleShaDrift`
28
+
-`formatConsoleShaDriftRefusal`
29
+
-`formatConsoleShaDriftWarning`
30
+
-`isConsoleVersionCompatible`
31
+
32
+
Nothing was deleted. `utils/console.ts` still exports all thirteen and every in-package caller still imports it directly; what these ten lost is only the ability to be named through a published specifier. `ResolveConsoleOptions` in particular is still `resolveConsolePath`'s parameter type, so the options object a caller passes keeps working structurally — only the type's name is no longer importable from this subpath.
33
+
34
+
`decideConsoleMount` and `createRuntimeAssetsPlugin` were retired on a measurement rather than by default: every reference to either name in this repo is inside `packages/cli`, the consumer-specifier ledger names neither, and `decideConsoleMount`'s own docblock scopes it to `isDev` and states that no published install can reach the refusal it exists to produce.
35
+
36
+
`packages/cli/test/published-subpath-console.pin.test.ts` now holds the packed `.d.ts` to exactly the three names and their shapes, compiled by a real consumer outside the workspace, with a control per retired name. Re-admitting any of the ten is a deliberate, reviewed, `minor`-bumped edit to that barrel and that pin.
37
+
38
+
<!-- adr-0087: not-required (no-migration-prescription) The ADR-0087 ledger serves metadata upgraders: its entries are the data source for `objectstack migrate meta`, `spec-changes.json` and the generated upgrade guide. All ten names are ordinary TypeScript values and types on a published subpath with no metadata surface whatsoever — no Zod schema, no `packages/spec` declaration, no stored representation — so `objectstack migrate meta` has nothing to reach and no ledger entry could carry anything. There is also nothing to prescribe, and the consumer reading behind that is stated here at exactly the strength it was measured. In this repo: no importer of any of the ten outside `packages/cli` itself. In `objectui`: a real zero, re-derived at the pinned `.objectui-sha` — the specifier `cli/console` does not occur, and none of the ten occurs as an identifier except `CONSOLE_PATH`, twice, both inside comment prose and neither an import — against a positive control of 545 lines that do import from the `@objectstack/` scope, so the corpus is live and the zero is a reading rather than a silence. In `cloud`: NOT MEASURED, which is not the same thing as zero — the code-search index does not cover that repository from this seat, answering 0 hits with `incomplete_results: true`, and no checkout of it is reachable either. Every channel tried refused, and an unreachable repository never reads as "no consumers"; a refusal is not an absence. So for `cloud` the evidence stays second-hand by construction: the consumer-specifier ledger, which names exactly the three kept helpers, and the ruling behind this card, which reads it the same way. The channel that would actually reach a surprised consumer is the compiler (TS2305 naming the retired symbol at the import site), which is more precise than a ledger line. -->
`@objectstack/trigger-record-change` and `@objectstack/trigger-schedule` now declare a `repository.directory` that resolves to the directory they actually live in.
7
+
8
+
Both manifests declared a path under `packages/plugins/` that no longer exists in the repository:
`repository.directory` is what npm uses to build the **Repository** deep link on a package page, and what tooling uses to locate a monorepo package's source from its tarball. Pointing it at a path that does not exist sends a reader to a 404 instead of to the source — on packages published today at `17.3.0`. The value ships inside the tarball, so this correction only reaches npm by being published; that is why it carries a changeset rather than `skip-changeset`.
16
+
17
+
The residue came from a three-commit sequence on 2026-06-12, and only one of those commits was a pure rename. `f15d6f6f6` **copied** the two packages to `packages/plugins/trigger-*` (26 files, +2222/-19, with all four directories briefly coexisting) and edited exactly one line of each copied manifest — its `name` — leaving `directory` pointing at the path it was copied from; `290c62514` deleted the originals five minutes later; and `ea4941ad8` then promoted `packages/plugins/trigger-*` to a first-class `packages/triggers/` directory as a pure 16-file rename with zero content changes, which made the declared value wrong in a second segment. Six weeks after that, `9a43e042f` (#3380) rewrote `repository.url` and `bugs` in both of these manifests, with the stale `directory` line sitting as unchanged context one line below the edited `url`. So the field was not merely never in anyone's way: one commit edited its immediate neighbour inside the same object, and a later reviewed hunk had the wrong line on screen. Nothing caught it because nothing reads it.
18
+
19
+
Scope of this change, stated as a measured set rather than a general claim: over all **81** tracked `package.json` files in the repository, **57** declare `repository.directory`; before this change **55** resolved to the manifest's own directory and **2** did not — the two above. After it, **57 of 57** resolve. No other manifest field is edited, and no package's code, exports or behaviour is touched. The remaining **24** manifests declare no `repository.directory` at all; that population is deliberately left alone here and is reported separately, because whether declaring the field is mandatory is a policy question rather than a correction.
0 commit comments