@objectstack/plugin-approvals 17.2.0 → 17.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +1144 -0
- package/dist/index.d.mts +1976 -807
- package/dist/index.d.ts +1976 -807
- package/dist/index.js +1198 -87
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1205 -90
- package/dist/index.mjs.map +1 -1
- package/package.json +23 -15
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,1149 @@
|
|
|
1
1
|
# @objectstack/plugin-approvals
|
|
2
2
|
|
|
3
|
+
## 17.4.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- 6530e04: A restored approval suspension can now be decided again, not only cancelled.
|
|
8
|
+
|
|
9
|
+
`AutomationEngine.restoreConsumedSuspension` re-arms the pause of a run that stranded mid-resume and tells the operator to *re-issue the continuation*. For an `approval` suspension nobody could: every approvals door that stamps the resume marker — `decide`, `recall`, `sendBack`, `resubmit` — guards on a `pending` request, and the row is terminal, written by the very call that stranded the run; and the generic engine door refuses an `approval` pause outright, because that node declares `resumeAuthority: 'service'`. The only remaining verb was `cancelRun`, which discards the branch's downstream work — so the advertised repair produced a run that looked resumable and was not decidable.
|
|
10
|
+
|
|
11
|
+
Measured against the real engine and the real decision door: the restored suspension lacks nothing. A `resumeAuthority`-marked resume walks the restored pause to completion. What was missing was an **issuer** on the approvals side, and that is what this adds.
|
|
12
|
+
|
|
13
|
+
- **`ApprovalService.continueRestoredRun(requestId, options?)`** re-issues the continuation the recorded outcome already produced once, against a pause an operator has re-armed. It reports which outcome it replayed, which edge it walked, and whether the signal was replayed exactly or rebuilt (`source: 'journal' | 'reconstructed'`).
|
|
14
|
+
- **The failing door now journals the signal it was carrying** on the repairable exit — the engine's own `status: 'stranded'` discriminator, the one exit that journals a repair snapshot — under `__strandedContinuation` in the request's `node_config_json`, beside the `__decisionOutputs` side-channel that was already there. Best-effort: it is awaited but can never replace the `RESUME_FAILED` throw the decision's caller is owed.
|
|
15
|
+
- **The continuation is tied to this request's own pause, by three guards.** A boolean "is this run suspended" is not enough: a run outlives any one request, so a terminal row's continuation could be issued against whatever pause the run happened to be sitting on. It now requires that the request is still the newest on its run, that a pause exists (strictly — an unreadable store throws rather than reading as "not suspended"), and that the pause is parked **where this request's recorded outcome was issued from**. That node is signal-aware, not simply the row's own: `approve`, `reject`, `revise` and `recall` are all issued at the request's own approval node, but a `resubmit` is only ever issued from the revise window the request's `revise` edge leads to, so its pause is re-armed there while the row still records the approval node. Comparing against the row's own node refused exactly that case, and told the operator the pause was not this request's when it was. The node check is fail-closed in every direction, including an engine that cannot report where a run is parked and a revise window this service cannot derive from the flow definition. This needs no new automation-engine surface: `listSuspendedRunsDurable` is already public, and the approvals-side resume interface simply declares it.
|
|
16
|
+
- **Runs stranded before this shipped are served too**, and where the signal cannot be proved the verb **refuses instead of guessing**. A status is not the same thing as a continuation, and three of the four terminal statuses have more than one writer or issuer: `approved` is unambiguous; `rejected` has two writers, discriminated by the `revise` action row that only ADR-0044's revision-limit auto-rejection leaves behind; `returned` has one writer but **two** issuers, discriminated by the `resubmit` action row whose sole writer is `resubmit` — without it a stranded resubmit was rebuilt as a send-back and walked the wrong edge, proceeding only through the engine's unmatched-label fallback with the wrong output; and `recalled` has two writers across **three** behaviours, two of which issue no continuation at all, so it is **refused on the rebuild path** with a message naming what an operator can do instead. Journal-recoverable is a **measured, named set** rather than a blanket claim: `approve`, `reject`, `resubmit` and `recall` continuations replay end to end through the verb, and `reject` and `resubmit` do so on the rebuild path as well. Two shapes are refused by design and stay refused — a `rejected` row that also carries a `revise` action, and a `recalled` row with no journal. NOT covered by a pin, and so not claimed: the `approve` rebuild path.
|
|
17
|
+
|
|
18
|
+
- **A journalled signal is checked against what the row's status can have issued, before it is replayed.** The journal records what the last FAILED resume was carrying, and nothing rewrites it when a later door moves the row on — so a signal can outlive the state that issued it. Measured, with no injected failure beyond the strand: a `resubmit` strands and journals `resubmit`; the submitter then recalls, a real `cancelRun` on an already-stranded run answers `false`, the row is marked `recalled` and the run stays parked; the restore re-arms the pause; and the stale `resubmit` was replayed, opening a fresh `pending` round on a request somebody deliberately withdrew. Every step an ordinary action answering ordinarily. A row is now replayable only for a continuation its own status can have issued — `approved`→`approve`, `rejected`→`reject`, `returned`→`revise` or `resubmit`, `recalled`→`recall`, and nothing at all for a status nobody has enumerated. ⛔ Clearing the journal after a successful replay does not close this and was measured not to: the offending replay is the FIRST replay of that journal, so a clear that fires afterwards can never run before the advance it would prevent.
|
|
19
|
+
|
|
20
|
+
⛔ What this deliberately does not do, each pinned: it does not re-open or rewrite the request row — all four `pending` guards are untouched and no status, mirror field or audit row is written, so a decided request still cannot be decided again through the front door; it does not relax `resumeAuthority: 'service'`, since the resume still goes through the one call site that stamps the marker; and it does not change `ApprovalDecisionResult`, whose shape is the subject of an open ruling. It also grants no capability in-process code did not already have — `RESUME_AUTHORITY_SERVICE` is importable by any host — what it adds is the guarded form, and the guards are stated as what they actually check: that this request is still the newest on its run, that a pause exists at all, that it is parked where this outcome was issued from, and that the recorded signal is one the row's present status can have issued. ⛔ None of them checks that the pause was consumed and genuinely re-armed, and an earlier wording of this entry claimed one did: a `returned` row with a resubmit action row and a pause that was never consumed is admitted, with `restoreConsumedSuspension` itself answering *"already resumable — nothing to restore"*. That shape is benign — the recorded action is the submitter's own resubmit, so the step it walks was decided — but it is not what any guard tests. Like the engine verb it completes, it is an in-process operator repair: no REST route, and no entry in the spec `ApprovalService` contract.
|
|
21
|
+
- 4c31f02: The stranded-request inspection tells a repairable strand from a cascade-failed run — through a dedicated read-only engine member, not through the wire (#15358, ruling B′).
|
|
22
|
+
|
|
23
|
+
`ApprovalService.inspectStrandedRequests` keyed on `run.status === 'failed'`, which over-reports in one direction: a **cascade-failed** run — an ancestor `failAncestors` failed while it was parked at its `subflow` node, whose pause `failSuspendedRun` consumed and journalled nothing — has the same terminal `failed` row as the #13909 strand, so both came back `runState: 'failed'`, and `restoreConsumedSuspension` re-arms one and refuses the other (`NO_CONSUMED_SUSPENSION`). The engine's discriminator (the consumed-suspension snapshot on the durable `RunRecord`) is deliberately NOT on the `ExecutionLogEntry` that `getRun` answers, because `GET /automation/:name/runs/:runId` serves that object verbatim — so the plugin could not read it, and reading its absence as "not a strand" would have called the repairable row dead.
|
|
24
|
+
|
|
25
|
+
**`@objectstack/service-automation` — additive, `minor`.** `AutomationEngine.inspectConsumedSuspension(runId)` answers whether `restoreConsumedSuspension` would have a consumed suspension to put back, from the SAME two witnesses that verb reads (this process's hot journal and the durable row, reconciled by the same `rowSupersedesJournal` / `persisted` / drop-notice rules — the read is now one private method both call), and re-arms nothing. Four answers, none folded: `repairable: true` (with the pause it would re-arm and which witness answered); `SNAPSHOT_DROPPED` (the strand happened, the store could not persist the snapshot, and this process holds no hot copy — repairable only by the replica that stranded it, while it lives); `NO_CONSUMED_SUSPENSION` (cascade-failed or never paused); `RUN_SUSPENDED` (already resumable). It REJECTS when a store cannot be read — an outage is unknown, not "nothing to restore". The result type is exported as `ConsumedSuspensionInspection`. `restoreConsumedSuspension` behaves exactly as before; nothing on `ExecutionLogEntry`, the run-detail route, or `@objectstack/spec` changes.
|
|
26
|
+
|
|
27
|
+
**`@objectstack/plugin-approvals` — additive on two published types, `minor`.**
|
|
28
|
+
|
|
29
|
+
- `ApprovalResumeSurface` gains the optional `inspectConsumedSuspension?(runId)`, declared the way `listSuspendedRunsDurable` is: a method `AutomationEngine` already implements, widening no engine surface.
|
|
30
|
+
- `StrandedRunState` splits `'failed'` three ways and keeps `'missing'` untouched: `'repairable'` (the #13909 strand — restore, then `continueRestoredRun`), `'snapshot_dropped'` (its own class: as `repairable` it over-reports, as `unrepairable` it is a false negative), and `'unrepairable'` (the cascade-failed / never-paused run, #15222's shape — nothing re-arms it). **`'failed'` stays a member, on purpose**: it is what a `failed` row is reported as when the attached surface has no `inspectConsumedSuspension` (an engine build older than this plugin, or a host double). Absence of the discriminator is fail-closed for a report — the row is reported, undifferentiated, never labelled `unrepairable` and never dropped. A thrown read counts `undetermined`, as the other two oracles' do.
|
|
31
|
+
|
|
32
|
+
A consumer switching exhaustively over `StrandedRunState` gains three arms; nothing it matched before stops arriving. The inspection's summary log adds `runRepairable` / `runSnapshotDropped` / `runUnrepairable` beside the existing counts.
|
|
33
|
+
- 3d3f60e: An approval decision that lands while its flow run strands now says so in fields, not only in prose.
|
|
34
|
+
|
|
35
|
+
`POST /api/v1/approvals/requests/{id}/reject` — and its sibling decision doors — could produce three coexisting outcomes from one call: the caller read HTTP 500, the request row **was** in its terminal status and had left the pending inbox, and the workflow run was stranded. A caller reading 500 has one honest inference available — "the rejection did not happen" — and it was the wrong one, so scripts and operators retried or escalated against a decision that was already durable. The only carrier of the truth was English prose in `error`, so finding the affected run meant regexing a run id out of a sentence, and nothing said whether that run could be repaired at all.
|
|
36
|
+
|
|
37
|
+
The 500 stays. A recorded decision whose flow never advances is still a failure and is still reported as one; the door does not become atomic and no decision is ever rolled back. What changed is that it stops discarding what the engine already said:
|
|
38
|
+
|
|
39
|
+
- **The `RESUME_FAILED` body gains four fields**, additively — `finalized` (always `true`: the decision stands), `decision`, `runId`, and `repairable`. Existing consumers see the same `code`, the same `error` and the same status.
|
|
40
|
+
- **`repairable` carries the engine's own discriminator** — `AutomationResult.status === 'stranded'`, the state stamped on exactly the exit that journals a repair snapshot. `false` is the answer for every other failure, including a lost run: absence of the signal is not repairability, and a repair verb that would refuse is worse than no promise.
|
|
41
|
+
- **`serviceResume` carries `status`** through to the door. It previously read only `success` / `code` / `error`, and the stranded exit reports a `status` and no `code` at all — so the platform's own repairability signal died one line before the envelope was built.
|
|
42
|
+
|
|
43
|
+
`@objectstack/types` gains `strandedDecisionFailure` / `strandedDecisionDetails` and the `StrandedDecisionDetails` type — the constructor and its recogniser in one module, so the producing service and the REST door cannot drift. A `RESUME_FAILED` raised without that carrier answers exactly the body it always did; the door never synthesises the envelope.
|
|
44
|
+
|
|
45
|
+
### Patch Changes
|
|
46
|
+
|
|
47
|
+
- ea03c7c: Fix: a `department` approver on a seeded business unit no longer routes the approval to another organization's members.
|
|
48
|
+
|
|
49
|
+
`ApprovalService.expandBusinessUnitUsers` screened the `sys_business_unit` rows with the null-inclusive tenant predicate (#3807 — a seeded unit carries no organization and is admitted on purpose) but read `sys_business_unit_member` with no organization predicate at all, under a system context that carries no tenant either. A seeded unit id exists identically in every tenant, so a `department:<id>` approver on tenant A's request resolved the shared unit and then collected every tenant's membership rows hanging off it — approval authority over A's record, routed to B's users. The member read now carries a strict `organization_id` equality against the directory organization the approver resolves in: the same screen `plugin-sharing` applies to these rows, and the same posture this package already takes for `sys_team_member` and `sys_user_position`.
|
|
50
|
+
|
|
51
|
+
The screen is strict rather than null-inclusive on purpose. `sys_business_unit_member.organization_id` is filled by REST/session writes but left NULL by seed replay and by elevated system-context writes (tracked in #14570), so a NULL on a membership row means unknown tenancy, not "platform-global", and routing fails closed on it. Declared cost: on a deployment whose membership rows (not merely its units) were seeded or system-written, a `department` approver on a request that carries an organization now expands to nobody — the slot falls to the `department:<id>` literal, the existing `expanded to nobody` warning (#3807) names it, and `onEmptyApprovers` governs the request as for any unstaffed target. The repair is to stamp those membership rows. A request that carries no organization is unchanged, and so is every unit-level screen.
|
|
52
|
+
- be92d46: These fourteen packages now declare `repository.directory`, so their npm pages carry a working "source" deep link to their own directory in the monorepo.
|
|
53
|
+
|
|
54
|
+
npm renders that field by concatenating it onto `repository.url`. None of these fourteen manifests carried a `repository` block at all, so every one of their npm pages offered no route from the package back to its code — not a broken link, no link. That is what this publishes: the block those pages read, naming each package's own directory.
|
|
55
|
+
|
|
56
|
+
Nothing else about these packages changes. No export, no runtime behaviour, no dependency and no file in the tarball other than the manifest's own `repository` key. The version bump exists because the fix is only real once it is published: the field lives in the manifest npm serves, so a corrected manifest sitting in the repository leaves the package page exactly as wrong as it was.
|
|
57
|
+
|
|
58
|
+
The rule behind it is now mechanical rather than remembered — `check:manifest-repository-directory` makes a publishable (non-private) workspace manifest declare the field naming its own directory, so a package added or moved after this cannot quietly go back to having no source link.
|
|
59
|
+
- ac6213e: Four server-side authorization sites stop deriving platform-operator authority from a NAME in `ExecutionContext.positions`, and read the ADR-0095 posture rung instead.
|
|
60
|
+
|
|
61
|
+
`positions[]` is the security axis, so it carries ADR-0057 D4 `sys_user_position` names alongside the built-ins. `sys_user_position` is `apiEnabled` and its `position` values are unconstrained, so a tenant could mint a row spelling `platform_admin` for one of their own users: `resolveUserAuthzGrants` pushed that name straight onto `grants.positions`, while `grants.posture` — derived from the unscoped `admin_full_access` grant and nothing else — correctly stayed `MEMBER`. Every reader of the name therefore answered `true` for a principal enforcement treats as an ordinary member. `resolve-authz-context.ts` states the rule at `hasPlatformAdminStanding` ("read the RUNG — never `positions.includes(...)`"), but a comment is not a gate and these four had not followed it.
|
|
62
|
+
|
|
63
|
+
Each site now tests `posture === 'PLATFORM_ADMIN'`, byte-for-byte what `hasPlatformAdminStanding` returns:
|
|
64
|
+
|
|
65
|
+
- **`plugin-sharing`** — `hasPlatformAuthority`. The minted row satisfied `assertResolvableAdminScope`, so an org-less caller holding only the ORG-scoped `manage_sharing` capability was answered with **every tenant's** sharing rules, and could delete platform-global rules. The `manage_platform_settings` capability spelling is unchanged.
|
|
66
|
+
- **`plugin-approvals`** — `isOverrideActor`. This predicate already read the rung and then ORed the name onto it, which is no protection: an OR is only as strong as its weakest arm. Because the platform arm deliberately crosses the tenant wall, the minted row let a member of one organization approve, reject or recall a **different organization's** pending request while holding no slot in its slate. The `ADMIN_FULL_ACCESS` capability arm and both TENANT_ADMIN arms are unchanged.
|
|
67
|
+
- **`runtime`** — the ADR-0126 §5 activation gate. Under a `group` or `isolated` posture this gate is the only thing between a tenant org admin and the **install-wide** `sys_metadata_activation` row, so the minted row reopened #10243 with a durable row behind it.
|
|
68
|
+
- **`plugin-security`** — `derivePosture` in the explain engine. Narrower than the other three, and stated precisely rather than overclaimed: the name-read sat behind an early `ctx.posture` return that `buildContextForUser` always populates, so the shipping path was already gated and a D4 row never moved it. What the read did reach was a posture-less hand-built context, where it made the panel **report** `PLATFORM_ADMIN` for a principal enforcement treats as a MEMBER — a misreport rather than an admission, but in the one tool an administrator opens to check exactly this.
|
|
69
|
+
|
|
70
|
+
No behaviour changes for a genuine platform operator: their resolved context carries the rung, and the built-in position is still projected onto `positions[]` for display and predicate use. What changes is that the name alone no longer answers the authorization question.
|
|
71
|
+
|
|
72
|
+
Graded `patch` on the surface it moves: no exported type, signature or contract changes, and no authorable metadata is added, removed or renamed. The only observable difference is that a principal who never held the capability grant stops being admitted — which is the defect, not a feature anyone could have depended on.
|
|
73
|
+
- 455d037: Documentation: `ApprovalService.recall`'s docblock summary line no longer claims the submitter is the only actor.
|
|
74
|
+
|
|
75
|
+
The block opened with "Withdraw a pending request (submitter only)" and then, three paragraphs down, stated the #3424 privileged override correctly — "The #3424 privileged override reaches a PENDING request only (#12775, maintainer ruling 2026-09-02)". Both cannot be true, and the code settles it in the paragraph's favour: `overrideAdmits` short-circuits the non-submitter guard on a `pending` request. A reader who finishes the block is not misled, but the summary line is the one an editor shows on hover and the one any single-line extraction takes.
|
|
76
|
+
|
|
77
|
+
The summary line now reads "Withdraw an undecided request." — status is the axis and the actor rules are left to the paragraphs that already state them correctly, the same structural move the `IApprovalService.recall` docstring makes on the spec side.
|
|
78
|
+
|
|
79
|
+
Prose only: no guard, no branch and no signature changed. It earns a changeset rather than `skip-changeset` because `@objectstack/plugin-approvals` publishes `dist/`, and this text ships inside the published `dist/index.d.ts` for `ApprovalService.recall`.
|
|
80
|
+
- 8c7cca1: `inspectStrandedRequests` no longer drops a row it could not differentiate, and no longer lets one misbehaving host abort the whole scan (#16709, items 2 and 3).
|
|
81
|
+
|
|
82
|
+
Both are the same mistake at two altitudes: the method exists to **enumerate** the terminal approval requests whose flow run cannot advance, so a failure to read the #15358 third oracle must never remove a row from the answer — and never remove the *other* rows either.
|
|
83
|
+
|
|
84
|
+
- **A thrown third read now leaves its row in `stranded`, as `'failed'`.** It used to be counted `undetermined` and skipped, exactly as a thrown `hasSuspendedRun` or `getRun` is. Those two are not the same question: a throw from either leaves it unknown *whether* the row is stranded at all, and a storage outage must not be published as a lost run. By the time the third oracle is asked, both have answered — no live pause, terminal `failed` — and it is asked only *which* of the three shapes the row is. A read that could not be made is therefore the textbook "could not differentiate", which is what `'failed'` already means (`StrandedRunState`, #15358 ruling item 1). Dropping the row let `stranded: []` read as "nothing stranded" while a row was in fact stuck, with a log line as its only trace; for a report, fail-closed means showing the row.
|
|
85
|
+
- **A host that violates `ApprovalResumeSurface` no longer aborts the scan.** `refineFailedRunState(verdict)` ran outside the `try` that wrapped the read, so an implementation resolving `undefined` where a verdict is declared threw a `TypeError` out of `inspectStrandedRequests` itself and the scan enumerated **nothing**. The refinement now runs inside that `try`; a malformed verdict costs its own row the differentiation, is counted `undetermined`, and costs every other row nothing.
|
|
86
|
+
|
|
87
|
+
⛔ No new `StrandedRunState` member and no widened export: both cases map onto the existing undifferentiated `'failed'`. The `undetermined` counter is kept as telemetry and now **overlaps** `stranded` by design — a row can be both reported and counted — so neither number alone sizes the scan's blind spot.
|
|
88
|
+
- Updated dependencies [fe0d9a4]
|
|
89
|
+
- Updated dependencies [ecd2158]
|
|
90
|
+
- Updated dependencies [f2b5e46]
|
|
91
|
+
- Updated dependencies [2ed6be6]
|
|
92
|
+
- Updated dependencies [ed7243d]
|
|
93
|
+
- Updated dependencies [6ba0db4]
|
|
94
|
+
- Updated dependencies [625b0c3]
|
|
95
|
+
- Updated dependencies [233222e]
|
|
96
|
+
- Updated dependencies [07f40e5]
|
|
97
|
+
- Updated dependencies [ceb4877]
|
|
98
|
+
- Updated dependencies [e9fcd6b]
|
|
99
|
+
- Updated dependencies [90e7e6d]
|
|
100
|
+
- Updated dependencies [2bdabe6]
|
|
101
|
+
- Updated dependencies [ca326b5]
|
|
102
|
+
- Updated dependencies [8f404a5]
|
|
103
|
+
- Updated dependencies [159dbad]
|
|
104
|
+
- Updated dependencies [68437d4]
|
|
105
|
+
- Updated dependencies [abb140c]
|
|
106
|
+
- Updated dependencies [8333a6c]
|
|
107
|
+
- Updated dependencies [3e3ecb0]
|
|
108
|
+
- Updated dependencies [3030369]
|
|
109
|
+
- Updated dependencies [d5d8d50]
|
|
110
|
+
- Updated dependencies [e08892d]
|
|
111
|
+
- Updated dependencies [ae05f2e]
|
|
112
|
+
- Updated dependencies [b548e43]
|
|
113
|
+
- Updated dependencies [c463d03]
|
|
114
|
+
- Updated dependencies [64bd6a3]
|
|
115
|
+
- Updated dependencies [13c48c2]
|
|
116
|
+
- Updated dependencies [b0529e1]
|
|
117
|
+
- Updated dependencies [66dc6ab]
|
|
118
|
+
- Updated dependencies [6f94458]
|
|
119
|
+
- Updated dependencies [6e67b86]
|
|
120
|
+
- Updated dependencies [132742f]
|
|
121
|
+
- Updated dependencies [85a2459]
|
|
122
|
+
- Updated dependencies [50dc214]
|
|
123
|
+
- Updated dependencies [e89fa92]
|
|
124
|
+
- Updated dependencies [e9fcd6b]
|
|
125
|
+
- Updated dependencies [8976ea1]
|
|
126
|
+
- Updated dependencies [56fe8c2]
|
|
127
|
+
- Updated dependencies [acabd24]
|
|
128
|
+
- Updated dependencies [ab50c8f]
|
|
129
|
+
- Updated dependencies [6491463]
|
|
130
|
+
- Updated dependencies [89cf4d6]
|
|
131
|
+
- Updated dependencies [21c5dcb]
|
|
132
|
+
- Updated dependencies [6d4d5d3]
|
|
133
|
+
- Updated dependencies [ed5d557]
|
|
134
|
+
- Updated dependencies [bca21f7]
|
|
135
|
+
- Updated dependencies [e9fcd6b]
|
|
136
|
+
- Updated dependencies [2025b1f]
|
|
137
|
+
- Updated dependencies [1a7a7c9]
|
|
138
|
+
- Updated dependencies [e9fcd6b]
|
|
139
|
+
- Updated dependencies [cbca47d]
|
|
140
|
+
- Updated dependencies [acf4d38]
|
|
141
|
+
- Updated dependencies [ef3a138]
|
|
142
|
+
- Updated dependencies [68d5dfd]
|
|
143
|
+
- Updated dependencies [3e21cf0]
|
|
144
|
+
- Updated dependencies [4cfc93b]
|
|
145
|
+
- Updated dependencies [098cbb7]
|
|
146
|
+
- Updated dependencies [efd6b43]
|
|
147
|
+
- Updated dependencies [859ded3]
|
|
148
|
+
- Updated dependencies [fa125f3]
|
|
149
|
+
- Updated dependencies [74628d9]
|
|
150
|
+
- Updated dependencies [a646120]
|
|
151
|
+
- Updated dependencies [6f1ce7d]
|
|
152
|
+
- Updated dependencies [7778115]
|
|
153
|
+
- Updated dependencies [86c75f4]
|
|
154
|
+
- Updated dependencies [2c753fe]
|
|
155
|
+
- Updated dependencies [52804cd]
|
|
156
|
+
- Updated dependencies [3f89967]
|
|
157
|
+
- Updated dependencies [53cf263]
|
|
158
|
+
- Updated dependencies [21aabbc]
|
|
159
|
+
- Updated dependencies [9c270bb]
|
|
160
|
+
- Updated dependencies [76c8c5a]
|
|
161
|
+
- Updated dependencies [088f761]
|
|
162
|
+
- Updated dependencies [a84e1ce]
|
|
163
|
+
- Updated dependencies [bf1054a]
|
|
164
|
+
- Updated dependencies [d8d2776]
|
|
165
|
+
- Updated dependencies [222dc0f]
|
|
166
|
+
- Updated dependencies [e9fcd6b]
|
|
167
|
+
- Updated dependencies [32c917d]
|
|
168
|
+
- Updated dependencies [f9a3c32]
|
|
169
|
+
- Updated dependencies [f502898]
|
|
170
|
+
- Updated dependencies [51ae731]
|
|
171
|
+
- Updated dependencies [af7edfe]
|
|
172
|
+
- Updated dependencies [b60f48b]
|
|
173
|
+
- Updated dependencies [c78c918]
|
|
174
|
+
- Updated dependencies [4ca358d]
|
|
175
|
+
- Updated dependencies [cf9bda4]
|
|
176
|
+
- Updated dependencies [784cb92]
|
|
177
|
+
- Updated dependencies [7629f4d]
|
|
178
|
+
- Updated dependencies [51df9fd]
|
|
179
|
+
- Updated dependencies [a7da4de]
|
|
180
|
+
- Updated dependencies [de0bcdd]
|
|
181
|
+
- Updated dependencies [70f7d6d]
|
|
182
|
+
- Updated dependencies [c677cda]
|
|
183
|
+
- Updated dependencies [6acb37e]
|
|
184
|
+
- Updated dependencies [7797102]
|
|
185
|
+
- Updated dependencies [554a160]
|
|
186
|
+
- Updated dependencies [f7da71e]
|
|
187
|
+
- Updated dependencies [7f745c3]
|
|
188
|
+
- Updated dependencies [0a038cc]
|
|
189
|
+
- Updated dependencies [e9fcd6b]
|
|
190
|
+
- Updated dependencies [97adce2]
|
|
191
|
+
- Updated dependencies [a83482c]
|
|
192
|
+
- Updated dependencies [5eb24f8]
|
|
193
|
+
- Updated dependencies [2a3decc]
|
|
194
|
+
- Updated dependencies [cc00df2]
|
|
195
|
+
- Updated dependencies [cc00df2]
|
|
196
|
+
- Updated dependencies [f4e6adf]
|
|
197
|
+
- Updated dependencies [ee4a59b]
|
|
198
|
+
- Updated dependencies [4db3c61]
|
|
199
|
+
- Updated dependencies [5ca314a]
|
|
200
|
+
- Updated dependencies [e0af1a8]
|
|
201
|
+
- Updated dependencies [4771bd9]
|
|
202
|
+
- Updated dependencies [414c1fc]
|
|
203
|
+
- Updated dependencies [22c0279]
|
|
204
|
+
- Updated dependencies [c930f85]
|
|
205
|
+
- Updated dependencies [0db2947]
|
|
206
|
+
- Updated dependencies [92b5d7f]
|
|
207
|
+
- Updated dependencies [613bfbd]
|
|
208
|
+
- Updated dependencies [abae16a]
|
|
209
|
+
- Updated dependencies [094b8fd]
|
|
210
|
+
- Updated dependencies [c7aca0d]
|
|
211
|
+
- Updated dependencies [c1d8f98]
|
|
212
|
+
- Updated dependencies [8e0b297]
|
|
213
|
+
- Updated dependencies [d4f9b2a]
|
|
214
|
+
- Updated dependencies [5f7fa1d]
|
|
215
|
+
- Updated dependencies [87f0ccc]
|
|
216
|
+
- Updated dependencies [aedbaef]
|
|
217
|
+
- Updated dependencies [a727043]
|
|
218
|
+
- Updated dependencies [c5d6803]
|
|
219
|
+
- Updated dependencies [10d05bb]
|
|
220
|
+
- Updated dependencies [69602e5]
|
|
221
|
+
- Updated dependencies [c3ce76c]
|
|
222
|
+
- Updated dependencies [7936b29]
|
|
223
|
+
- Updated dependencies [46803fa]
|
|
224
|
+
- Updated dependencies [c2a336c]
|
|
225
|
+
- Updated dependencies [9f890d3]
|
|
226
|
+
- Updated dependencies [0bb2318]
|
|
227
|
+
- Updated dependencies [f7db8f4]
|
|
228
|
+
- Updated dependencies [1ecee3e]
|
|
229
|
+
- Updated dependencies [9408b7f]
|
|
230
|
+
- Updated dependencies [2bb0614]
|
|
231
|
+
- Updated dependencies [b3820c3]
|
|
232
|
+
- Updated dependencies [e9fcd6b]
|
|
233
|
+
- Updated dependencies [4df2a98]
|
|
234
|
+
- Updated dependencies [9bcd9be]
|
|
235
|
+
- Updated dependencies [b398ad2]
|
|
236
|
+
- Updated dependencies [99261a7]
|
|
237
|
+
- Updated dependencies [81b426f]
|
|
238
|
+
- Updated dependencies [001af1c]
|
|
239
|
+
- Updated dependencies [fb77aa5]
|
|
240
|
+
- Updated dependencies [3d3f60e]
|
|
241
|
+
- Updated dependencies [581d8f8]
|
|
242
|
+
- Updated dependencies [f81afe3]
|
|
243
|
+
- Updated dependencies [40a44b9]
|
|
244
|
+
- Updated dependencies [f89812e]
|
|
245
|
+
- Updated dependencies [7a7fb03]
|
|
246
|
+
- Updated dependencies [8fd246d]
|
|
247
|
+
- Updated dependencies [021a735]
|
|
248
|
+
- Updated dependencies [7bdb163]
|
|
249
|
+
- @objectstack/spec@17.4.0
|
|
250
|
+
- @objectstack/core@17.4.0
|
|
251
|
+
- @objectstack/platform-objects@17.4.0
|
|
252
|
+
- @objectstack/formula@17.4.0
|
|
253
|
+
- @objectstack/types@17.4.0
|
|
254
|
+
- @objectstack/metadata-core@17.4.0
|
|
255
|
+
|
|
256
|
+
## 17.3.0
|
|
257
|
+
|
|
258
|
+
### Minor Changes
|
|
259
|
+
|
|
260
|
+
- dda969c: fix(approvals): a deleted record's pending approvals auto-cancel instead of stranding in the inbox (#13568)
|
|
261
|
+
|
|
262
|
+
Deleting a record left every `pending` approval it had opened sitting in the
|
|
263
|
+
approvers' inbox — counted in the pending total, openable, and pointing at a
|
|
264
|
+
`record_id` that resolves to nothing. Nothing about it was module-specific:
|
|
265
|
+
an approval node that declares `lockRecord` blocks the EDIT, so "delete and
|
|
266
|
+
recreate" is the only route left to an author who needs to fix a submitted
|
|
267
|
+
record, and every such delete added another orphan. Maintainer ruling
|
|
268
|
+
2026-08-31 (`总监席第 5 场决裁批 #5`, verbatim 「同意」): pending requests
|
|
269
|
+
auto-cancel on record delete — status `cancelled` plus a machine-readable
|
|
270
|
+
reason, rows KEPT for audit, out of the pending count and the inbox's default
|
|
271
|
+
view.
|
|
272
|
+
|
|
273
|
+
**Graded `minor`, and deliberately not `patch`.** The repair itself is a
|
|
274
|
+
defect fix, but it lands by WIDENING two published vocabularies and adding a
|
|
275
|
+
declared column, and this repo's convention grades a shipped service's
|
|
276
|
+
accept-set/behaviour move as `minor`. **No `BREAKING` banner**: nothing is
|
|
277
|
+
narrowed and no metadata that used to be accepted is now refused — the one
|
|
278
|
+
consequence a consumer can feel is that `ApprovalStatus` and
|
|
279
|
+
`ApprovalActionKind` each gained a member, so an exhaustive `switch` with no
|
|
280
|
+
default, or a `satisfies Record<ApprovalStatus, …>` map outside this repo,
|
|
281
|
+
now has a case to add. That is the same shape `returned` had when ADR-0044
|
|
282
|
+
landed it.
|
|
283
|
+
|
|
284
|
+
**Spec (`@objectstack/spec/contracts/approval-service`)**
|
|
285
|
+
|
|
286
|
+
- `APPROVAL_STATUSES` gains `cancelled` (+ its `APPROVAL_STATUS_LABELS`
|
|
287
|
+
entry). Its own terminal state rather than a re-use of `recalled`: a recall
|
|
288
|
+
is an ACT by the submitter, and filing a platform-initiated void as one
|
|
289
|
+
attributes a withdrawal to a person who never performed it.
|
|
290
|
+
- New `APPROVAL_CANCEL_REASONS` / `ApprovalCancelReason` /
|
|
291
|
+
`APPROVAL_CANCEL_REASON_LABELS`, single entry `record_deleted`. A
|
|
292
|
+
VOCABULARY, not free text, because the reason has a non-human consumer (the
|
|
293
|
+
inbox and the tombstone presentation branch on it) — and a CLASS, per the
|
|
294
|
+
ruling's wording, so the next platform-initiated cancellation cause extends
|
|
295
|
+
this list instead of minting a second terminal status for itself.
|
|
296
|
+
- `APPROVAL_ACTION_KINDS` gains `cancel` — the only kind with no human actor,
|
|
297
|
+
by construction.
|
|
298
|
+
- `ApprovalRequestRow.cancel_reason` declared, optional-nullable.
|
|
299
|
+
|
|
300
|
+
**Plugin (`@objectstack/plugin-approvals`)**
|
|
301
|
+
|
|
302
|
+
- `sys_approval_request.cancel_reason`, a select derived from the contract
|
|
303
|
+
vocabulary and never re-typed (the #3786 rule the `status` column already
|
|
304
|
+
follows). On the row rather than on the audit entry, so a plain list view
|
|
305
|
+
can read WHY without joining the append-only action log.
|
|
306
|
+
- `bindRecordDeleteCancelHook` — a GLOBAL `afterDelete` registration beside
|
|
307
|
+
the existing global record-lock hook, so one platform-level linkage covers
|
|
308
|
+
every "approval + `lockRecord`" object at once. It needs no row-set
|
|
309
|
+
plumbing: the engine binds the deleted row's pre-image on the by-id path
|
|
310
|
+
and fans `afterDelete` out per matched row on a predicate delete, so a bulk
|
|
311
|
+
delete is covered by the same handler. The approvals tables are excluded at
|
|
312
|
+
registration, so they do not pay the delete-side pre-image read.
|
|
313
|
+
- `ApprovalService.cancelForDeletedRecord` writes the transition: one
|
|
314
|
+
append-only `sys_approval_action` row (`action: 'cancel'`, no actor),
|
|
315
|
+
`status: 'cancelled'` + `cancel_reason: 'record_deleted'` +
|
|
316
|
+
`completed_at`, and a `sys_approval_approver` index clear — that last one
|
|
317
|
+
is not optional garnish, it is what actually empties the inbox, because the
|
|
318
|
+
approver filter resolves through that index rather than through `status`.
|
|
319
|
+
- The `Completed` list view now includes `cancelled`, so a kept audit row is
|
|
320
|
+
visible in the one curated terminal view rather than only under `All`.
|
|
321
|
+
- ⛔ **No flow resume and no status mirror-back.** A cancellation is a status
|
|
322
|
+
write plus a reason, not a decision, so there is no branch to resume down.
|
|
323
|
+
The mirror-back is skipped by construction rather than by a swallowed
|
|
324
|
+
error: it is an `update_record` against the row that was just deleted — the
|
|
325
|
+
exact write this card's forensics caught failing elsewhere. The suspended
|
|
326
|
+
run the request gated is reported at `warn` with its id and otherwise left
|
|
327
|
+
alone; what becomes of it belongs to the automation service.
|
|
328
|
+
- ⛔ **The delete is never refused.** The "forbid delete while an approval is
|
|
329
|
+
pending" direction was vetoed in the same ruling — `lockRecord` already
|
|
330
|
+
blocks the edit, and blocking the delete too locks an author onto a record
|
|
331
|
+
they cannot fix. Nothing in the hook throws; a failure degrades to the
|
|
332
|
+
pre-existing state (the stale row) and is logged.
|
|
333
|
+
- Terminal rows are untouched. `approved` / `rejected` / `recalled` /
|
|
334
|
+
`returned` requests about the deleted record keep their recorded outcome —
|
|
335
|
+
history stays history, and rendering their now-dead record reference is a
|
|
336
|
+
separate console-side change.
|
|
337
|
+
|
|
338
|
+
zh-CN / ja-JP / es-ES bundles carry authored translations for the new leaves
|
|
339
|
+
(已作废 / 無効化済み / Anulada), not source fills.
|
|
340
|
+
- 277948f: feat(spec,plugin-approvals): `escalation.enabled` defaults to `true` and the SLA sweep finally reads it (#12278)
|
|
341
|
+
|
|
342
|
+
**BREAKING** semantic default flip on a published authorable key, shipped as
|
|
343
|
+
`minor` under the repo's launch-window convention for breaking changes.
|
|
344
|
+
Maintainer ruling 2026-08-27 (Option C), explicitly reversing the 2026-08-26
|
|
345
|
+
"spec stays as declared" ruling with fresh analysis.
|
|
346
|
+
|
|
347
|
+
`ApprovalEscalationSchema.enabled` declared `default(false)` while the
|
|
348
|
+
plugin-approvals escalation sweep never read the key: any escalation block
|
|
349
|
+
with a positive `timeoutHours` escalated, and with `action: 'auto_approve'`
|
|
350
|
+
that silently approved requests their author had declared off the clock —
|
|
351
|
+
the ADR-0049 declared-but-unenforced shape, failing open. Worse, the
|
|
352
|
+
approval-node executor parses node config through the schema before
|
|
353
|
+
snapshotting it onto the request row, so the old default **materialized**
|
|
354
|
+
`enabled: false` into storage for every author who omitted the key, making
|
|
355
|
+
"authored off" and "defaulted off" byte-identical at the sweep site.
|
|
356
|
+
|
|
357
|
+
One change, both halves:
|
|
358
|
+
|
|
359
|
+
- **spec**: `enabled` now defaults to `true` (stays `z.boolean()`; no
|
|
360
|
+
tri-state). The feature-level switch is whether an `escalation` block
|
|
361
|
+
exists at all; within a block carrying `timeoutHours`, escalation is on
|
|
362
|
+
unless explicitly turned off — which is what the runtime, its eleven
|
|
363
|
+
behaviour tests, and every teaching surface have always meant. Declared in
|
|
364
|
+
`DEFAULT_CHANGES_BY_MAJOR` (17) and registered as the
|
|
365
|
+
`approval-escalation-enabled-default-flip` semantic migration entry.
|
|
366
|
+
- **runtime**: `runEscalations` skips a request whose snapshot carries an
|
|
367
|
+
explicit `escalation.enabled === false` — the declared switch is enforced.
|
|
368
|
+
Request snapshots created **before** the flip cutoff
|
|
369
|
+
(`ESCALATION_ENABLED_FLIP_CUTOFF_MS`, 2026-08-28T00:00:00Z) ride a
|
|
370
|
+
read-side legacy window and keep escalating exactly as they do today: their
|
|
371
|
+
stored `false` is overwhelmingly the old schema default materialized onto an
|
|
372
|
+
author who never wrote the key, every such stored row is escalating today,
|
|
373
|
+
and the window retires itself as pending requests drain — zero tenant rows
|
|
374
|
+
rewritten.
|
|
375
|
+
|
|
376
|
+
Deployed metadata that omits `enabled` does not change behaviour (it
|
|
377
|
+
escalated before, it escalates after). What changes is that writing
|
|
378
|
+
`enabled: false` finally binds for newly opened requests — the declared
|
|
379
|
+
intent being honoured. To keep an SLA off, write `enabled: false`; to
|
|
380
|
+
escalate on timeout, an `escalation` block with `timeoutHours` is enough.
|
|
381
|
+
|
|
382
|
+
<!-- adr-0087: registered approval-escalation-enabled-default-flip -->
|
|
383
|
+
- 3690922: fix(plugin-approvals): the non-submitter recall refusal renders through the
|
|
384
|
+
Operation Message Catalog instead of a hardcoded English sentence (#11993, the
|
|
385
|
+
services-side half of the shape-A ruling)
|
|
386
|
+
|
|
387
|
+
A user who opened a record someone else had submitted for approval, clicked
|
|
388
|
+
Recall and was correctly refused read the reason in English regardless of their
|
|
389
|
+
own locale. `@objectstack/rest`'s `handleApprovalError` ships this service's
|
|
390
|
+
thrown reason as the 403 body's human-readable `error`, and Console splices it
|
|
391
|
+
under its own localized label — so an operator in a fully Chinese deployment
|
|
392
|
+
read a Chinese prefix glued onto an English sentence they could not act on
|
|
393
|
+
(`撤回审批失败: <English>`).
|
|
394
|
+
|
|
395
|
+
The refusal now renders through the shared Operation Message Catalog in
|
|
396
|
+
`@objectstack/spec/system` under the key `approval_recall_not_submitter` that
|
|
397
|
+
#12493 landed for it — the same mechanism `plugin-security`'s denial gates
|
|
398
|
+
already use, with the same resolution ladder (deployment override → the
|
|
399
|
+
caller's locale → `en` → the key) and the same guarantee that a misbehaving
|
|
400
|
+
i18n service cannot turn a 403 into a 500. All four platform locales (`en`,
|
|
401
|
+
`zh-CN`, `ja-JP`, `es-ES`) ship copy that names who *can* recall, rather than
|
|
402
|
+
dead-ending the reader.
|
|
403
|
+
|
|
404
|
+
`ApprovalServiceOptions` gains an optional `messageTranslator` — a lazily
|
|
405
|
+
resolved, `II18nService.t`-compatible lookup, wired by `ApprovalsServicePlugin`
|
|
406
|
+
the same way `tenancyPosture` and the field-visibility source are, because the
|
|
407
|
+
i18n service is contributed by another plugin and may start later. It is what
|
|
408
|
+
makes the override address the catalog documents,
|
|
409
|
+
`errors.approval_recall_not_submitter`, actually take effect for this emitter;
|
|
410
|
+
a stack without an i18n service still renders the built-in catalog in the
|
|
411
|
+
caller's locale.
|
|
412
|
+
|
|
413
|
+
**Not changed: who may recall an approval.** The gate is byte-identical — the
|
|
414
|
+
submitter, or a privileged admin releasing a stuck record (#3424). Only the
|
|
415
|
+
sentence the refusal carries is different, and the `FORBIDDEN:` code prefix
|
|
416
|
+
that the REST layer maps to 403 is untouched.
|
|
417
|
+
|
|
418
|
+
The button-visibility half of #11993 — a non-submitter seeing a live recall
|
|
419
|
+
button at all — is not addressed here; see the issue for the measurement.
|
|
420
|
+
- 599515d: Four more system objects declare their polymorphic pointer pair (#11386, ADR-0052 §5, adopting the carrier #11339 landed): `sys_audit_log.record_id`, `sys_approval_request.record_id`, `sys_record_share.record_id` and `sys_share_link.record_id` now carry `referenceVia: 'object_name'`. A seed row addressing one of these by the target's natural key resolves against the object its sibling column names, per row — so a packaged app can ship audit history, pending approvals, record grants and share links that actually attach to the records they are about, and the queries that give each row its meaning (the `{object_name, record_id}` index, the pending-request lock, the sharing middleware's grant lookup, the share link's fail-closed record-existence gate) match on the target's real id.
|
|
421
|
+
|
|
422
|
+
The accept/reject contract changes with it on those four objects, deliberately and in the already-ruled direction: an unresolvable pointer on a DECLARED pair is a loud, counted failure instead of the old silent verbatim store. On a grant table that is the sharper win — a share whose `record_id` stayed a natural key enforced nothing while displaying as a grant, and was then deleted by the orphan sweep for describing a record that does not exist. Internal-id-shaped values still pass through verbatim, so a demo row about an already-deleted record (an `action: 'delete'` audit row) stays authorable. Undeclared text columns are untouched.
|
|
423
|
+
|
|
424
|
+
The fifth object surveyed, `sys_automation_run` (`trigger_object` / `trigger_record_id`), deliberately STAYS UNDECLARED. Its pair has the same shape but its rows are not content about a record: a `paused` row is a live continuation the engine rehydrates on boot, terminal rows are telemetry under a 30-day sweep, and the object has no natural key to address rows by. The verdict, its reasons, and what would have to change to flip it are recorded on the field itself and pinned by a test.
|
|
425
|
+
|
|
426
|
+
### Patch Changes
|
|
427
|
+
|
|
428
|
+
- 1007134: fix(approvals): the #3424 override reaches a `pending` request only — override-recall of a `returned` request is now refused (#12775)
|
|
429
|
+
|
|
430
|
+
**Behaviour change, declared explicitly (maintainer ruling 2026-09-02) so that
|
|
431
|
+
one revert of this changeset's PR restores the previous behaviour.**
|
|
432
|
+
|
|
433
|
+
`ApprovalService.recall` admits two callers: the submitter, and a platform or
|
|
434
|
+
tenant admin releasing a stuck request (the #3424 privileged override). Recall
|
|
435
|
+
is also valid on the LATEST `returned` request of a run — the ADR-0044 revise
|
|
436
|
+
window, where the submitter abandons the revision instead of resubmitting.
|
|
437
|
+
Those two rules met above the state check: the override short-circuit carried
|
|
438
|
+
no status test of its own, so an override actor could recall a `returned`
|
|
439
|
+
request too. Nothing else on the platform said so — `isOverrideActor`'s doc
|
|
440
|
+
block names a PENDING request, `attachViewers` computes
|
|
441
|
+
`viewer.can_override` as `status === 'pending' && isOverrideActor(...)`, and
|
|
442
|
+
the `approval_recall` action's override arm reads that flag — so the reach was
|
|
443
|
+
API-only, never offered by any UI, and pinned by nothing.
|
|
444
|
+
|
|
445
|
+
What changes:
|
|
446
|
+
|
|
447
|
+
- **Override-recall of a `returned` request is refused.** The override
|
|
448
|
+
short-circuit in `recall` now applies only while the request is `pending`,
|
|
449
|
+
spelled exactly as the viewer flag is computed. On `returned` an override
|
|
450
|
+
actor is judged exactly as any other non-submitter and receives the existing
|
|
451
|
+
refusal: `403 FORBIDDEN` over REST, with the operation catalog's
|
|
452
|
+
`approval_recall_not_submitter` sentence. No new error code, no new envelope.
|
|
453
|
+
- **Only `pending` requests are override-recallable** — the same scope as the
|
|
454
|
+
other three override levers (approve / reject / reassign are pending-only at
|
|
455
|
+
their endpoints), and the same scope the viewer flag has always declared.
|
|
456
|
+
|
|
457
|
+
What does not change:
|
|
458
|
+
|
|
459
|
+
- The submitter's own recall of a `returned` request (the ADR-0044 revise
|
|
460
|
+
window) is untouched; so is the submitter's recall of a `pending` one.
|
|
461
|
+
- The override actor's recall of a `pending` request — the #3424 rescue of a
|
|
462
|
+
request routed to an unstaffed position — is untouched, lock release and all.
|
|
463
|
+
- The refusal's message, wire code and developer log line keep their shape; the
|
|
464
|
+
log line now also names the request status it refused on.
|
|
465
|
+
|
|
466
|
+
Why: the gate now agrees with the viewer flag and the documented contract at
|
|
467
|
+
one point instead of disagreeing with both. If a real operator workflow
|
|
468
|
+
depended on override-recalling a `returned` request, this is the change to
|
|
469
|
+
revert; the `returned` record lock is already released, so the stuck-record
|
|
470
|
+
rescue motive that justifies the override does not apply on that status.
|
|
471
|
+
- b579b03: fix(approvals): refuse loudly when a successful mutation's read-back is org-filtered out (#12769)
|
|
472
|
+
|
|
473
|
+
Ten ApprovalService result sites (decide/decideNode, recall, sendBack, resubmit,
|
|
474
|
+
reassign, remind, requestInfo, comment) read the row they just mutated back
|
|
475
|
+
through the caller's organization narrowing and asserted the result non-null
|
|
476
|
+
(`fresh!`). For an org-less request row — produced by construction on every
|
|
477
|
+
schedule / time-relative / api trigger run (#10131; pinned rather than repaired
|
|
478
|
+
by #9132) — an org-scoped caller's read-back matches nothing, so a call that
|
|
479
|
+
SUCCEEDED shipped a well-formed success envelope whose declared-non-null
|
|
480
|
+
`request` was `null` (HTTP 200 with `"request": null` through the REST
|
|
481
|
+
pass-through), and a client dereferencing `request.status` crashed.
|
|
482
|
+
|
|
483
|
+
The read-back now throws `READ_BACK_FAILED: …` instead: the write is recorded
|
|
484
|
+
and NOT rolled back; only the result echo is refused, loudly. Over REST the
|
|
485
|
+
error surfaces through each route's existing 500 arm (`APPROVAL_RECALL_FAILED`
|
|
486
|
+
and siblings — already-registered codes) with the `READ_BACK_FAILED:` message
|
|
487
|
+
in the body. No declared result type changed — the declared-non-null `request`
|
|
488
|
+
is now always true because a result that cannot be built is never returned. The
|
|
489
|
+
caller-org narrowing in `loadRequest` (tenancy wall) is deliberately untouched.
|
|
490
|
+
- 2a05851: fix(plugin-approvals): the `approval_recall` action shows for the #3424 admin override (#12716)
|
|
491
|
+
|
|
492
|
+
`ApprovalService.recall` has admitted two callers since #3424 — the submitter,
|
|
493
|
+
and a platform/tenant admin releasing a stuck request — and `isOverrideActor`'s
|
|
494
|
+
own doc block names recall as one of the four override levers in so many words.
|
|
495
|
+
The declared action that reaches that endpoint did not agree: `approval_recall`'s
|
|
496
|
+
`visible` predicate was submitter-only, while its three siblings
|
|
497
|
+
(`approval_approve` / `approval_reject` / `approval_reassign`) each OR in
|
|
498
|
+
`record.viewer.can_override`.
|
|
499
|
+
|
|
500
|
+
So recall was the one lever the override covers whose button never appeared. An
|
|
501
|
+
admin rescuing an approval routed to an unstaffed position could approve or
|
|
502
|
+
reject their way out — writing a decision nobody made — or reassign it, but
|
|
503
|
+
could not simply withdraw it. This is declared-vs-enforced drift in the less
|
|
504
|
+
usual direction: a capability the server grants that no UI entry exposed.
|
|
505
|
+
|
|
506
|
+
`approval_recall`'s `visible` now ORs in `record.viewer.can_override`, spelled
|
|
507
|
+
byte-identically to the three siblings.
|
|
508
|
+
|
|
509
|
+
Not a permission change: the service's authorisation set is untouched, and
|
|
510
|
+
`can_override` was already computed server-side for every viewer.
|
|
511
|
+
|
|
512
|
+
**Pending-only, and enforced rather than asserted.** The new arm carries no
|
|
513
|
+
status test of its own — neither do the siblings — because the flag is already
|
|
514
|
+
status-scoped where it is computed: `attachViewers` sets
|
|
515
|
+
`can_override: row.status === 'pending' && isOverrideActor(...)`, ANDed, so the
|
|
516
|
+
flag can never be true off `pending` and the arm is pending-only in effect
|
|
517
|
+
however CEL groups the expression. The submitter's own `returned` (revise
|
|
518
|
+
window) arm is unchanged. Pinned in both directions, with the flag's own scoping
|
|
519
|
+
pinned against the real service on a genuinely `returned` row.
|
|
520
|
+
- 9bc403e: Correct what `sys_approval_request.payload_json` is documented to be FOR — it is audit evidence served redacted per reader, not a notification source
|
|
521
|
+
|
|
522
|
+
The object's module docstring — which ships to consumers in the package's type
|
|
523
|
+
declarations — justified the snapshot column with: *"used by notifications so
|
|
524
|
+
they can render before the record is locked or changed."* That consumer does
|
|
525
|
+
not exist. Measured against every `this.notify(...)` call site in
|
|
526
|
+
`approval-service.ts`, all **12** of them, each passes a payload of
|
|
527
|
+
`{ title, message, actionUrl }` (two also carry `actions`), built from
|
|
528
|
+
`object_name` / `record_id` and the caller's own comment. **None** reads
|
|
529
|
+
`payload_json` or the parsed `payload`.
|
|
530
|
+
|
|
531
|
+
This is more than tidiness: that sentence was the only documented
|
|
532
|
+
justification for the column holding a *full* row, and it was cited as such
|
|
533
|
+
during the #10749 consumer inventory before anyone checked it. The docstring
|
|
534
|
+
now states the real reason — the snapshot is retained as **audit evidence of
|
|
535
|
+
what was actually submitted**, so the column stays whole at rest, and is served
|
|
536
|
+
**redacted per reader** by the subject object's field-level read controls via
|
|
537
|
+
`getReadableFields`, on the approvals-inbox door and the generic data door
|
|
538
|
+
alike (#11039).
|
|
539
|
+
|
|
540
|
+
The field's own `description` is deliberately unchanged: `Record snapshot at
|
|
541
|
+
submission time` is accurate, and it — unlike the JSDoc — is the string
|
|
542
|
+
extracted into the four generated i18n bundles, so no translation leaf moves
|
|
543
|
+
and no locale is left holding an English seed.
|
|
544
|
+
|
|
545
|
+
Also carried in the same pass, the residual documentation the #10749 closure
|
|
546
|
+
assigned to the next docs touch in this lane: `payload-redaction.ts` recorded
|
|
547
|
+
`hidden`-vs-serialization as an **open** `packages/spec` question, and it has
|
|
548
|
+
since been ruled (maintainer, 2026-08-24, applying the 2026-08-12 lineage).
|
|
549
|
+
That paragraph now states the ruling — **`hidden: true` stays UI-only;
|
|
550
|
+
`internal: true` is the serialization primitive** — so an author who needs a
|
|
551
|
+
field kept out of read results is pointed at `internal: true` (#7728,
|
|
552
|
+
ADR-0049) rather than at `hidden`, which never governed serialization.
|
|
553
|
+
|
|
554
|
+
Documentation only: no runtime behaviour, no schema field, and no public type
|
|
555
|
+
signature changes.
|
|
556
|
+
- 59c0891: fix(plugin-approvals): the stranded-request inspection now sees a run that FAILED mid-resume (#13909)
|
|
557
|
+
|
|
558
|
+
`ApprovalService.inspectStrandedRequests` was structurally blind to the shape
|
|
559
|
+
#13909 owns, and reported `0` for it — the one shape an operator most needs to
|
|
560
|
+
see.
|
|
561
|
+
|
|
562
|
+
**The mechanism.** `AutomationEngine.resumeInternal` consumes the suspension
|
|
563
|
+
*before* running the downstream nodes: `forgetSuspendedRun(run, 'resumed')`
|
|
564
|
+
precedes `traverseNext`. A downstream node that merely THREW therefore threw
|
|
565
|
+
with the pause already gone, the catch arm recorded the run `failed`, and
|
|
566
|
+
nothing can resume it again (`resume` answers `RUN_NOT_FOUND`, `cancelRun` is a
|
|
567
|
+
no-op). The decision is durable and the flow stopped half-way.
|
|
568
|
+
|
|
569
|
+
**Why the inspection could not see it.** Its second oracle was
|
|
570
|
+
`if (terminal) continue` — the existence of ANY run-history row ended the check,
|
|
571
|
+
on the reading "the run ran to a terminal state, it is not dangling". But the
|
|
572
|
+
terminal row here is written BY the failure that stranded the request, so the
|
|
573
|
+
evidence of the defect was read as evidence of health. `releaseDeadRunRequests`
|
|
574
|
+
cannot see it either: it scans `status: 'pending'`, and the decision is what
|
|
575
|
+
took the row out of `pending`.
|
|
576
|
+
|
|
577
|
+
**The widening, and its limits.** The second oracle now classifies the run
|
|
578
|
+
instead of merely detecting it. A `failed` run is reported; `completed`,
|
|
579
|
+
`cancelled` and `paused` are each still skipped, one named reason at a time, and
|
|
580
|
+
a status this code does not recognise is skipped too — the spec's
|
|
581
|
+
`ExecutionStatus` vocabulary is wider than the four statuses the engine writes,
|
|
582
|
+
and a future status must not become a silent false positive. `paused` in
|
|
583
|
+
particular stays skipped because "the suspension is gone but no terminal row is
|
|
584
|
+
written yet" is exactly what a resume IN FLIGHT looks like. The first oracle is
|
|
585
|
+
unchanged: a run the suspension store still holds is alive, and an unreadable
|
|
586
|
+
store is still counted `undetermined`, never condemned.
|
|
587
|
+
|
|
588
|
+
Reported rows now carry `runState: 'missing' | 'failed'` (new exported type
|
|
589
|
+
`StrandedRunState`), because the two shapes need different remedies: a `missing`
|
|
590
|
+
run has no history to read, a `failed` one has a step log and an error naming
|
|
591
|
+
the node that threw. The sweep's own warning splits its counts the same way.
|
|
592
|
+
`StrandedApprovalRequest` is an output-only reporting shape the service
|
|
593
|
+
produces; the added field is not constructed by any caller in this repo.
|
|
594
|
+
|
|
595
|
+
**Still read-only, and still not a census.** No status is changed and no run is
|
|
596
|
+
cancelled — the decision genuinely happened. This makes the condition *visible*
|
|
597
|
+
in a deployment; how many runs are already in it can only be answered against
|
|
598
|
+
that deployment's own tables. Nothing here changes the resume ordering, which
|
|
599
|
+
is #13909's own next slice.
|
|
600
|
+
- 4bb09e8: feat(service-automation): an operator can put back a suspension a failed resume consumed (#13909)
|
|
601
|
+
|
|
602
|
+
A run that was resumed and whose downstream node merely **threw** was
|
|
603
|
+
terminally unresumable, and nothing anywhere could move it. The engine consumes
|
|
604
|
+
the suspension *before* running downstream nodes, so such a node throws with
|
|
605
|
+
the pause already gone and the catch arm records the run `failed`: `resume`
|
|
606
|
+
then answers `RUN_NOT_FOUND`, `cancelRun` is a no-op, and none of the engine's
|
|
607
|
+
other public methods takes the run anywhere. A deployment could enter that
|
|
608
|
+
state and never leave it.
|
|
609
|
+
|
|
610
|
+
`AutomationEngine.restoreConsumedSuspension(runId, { requestedBy, reason })`
|
|
611
|
+
is the exit. It puts the consumed suspension back — verbatim, as it stood at
|
|
612
|
+
the pause — so the run is resumable again through an ordinary `resume`, with
|
|
613
|
+
the same authority gate, the same screen validation and the same idempotency
|
|
614
|
+
guard as any other.
|
|
615
|
+
|
|
616
|
+
- **Deliberate, never automatic.** Nothing calls it on its own: no retry, no
|
|
617
|
+
sweeper. An operator asks for one run, by id.
|
|
618
|
+
- **Safe to refuse, with the reason named.** A run that is still suspended, one
|
|
619
|
+
whose resume is *in flight*, one that completed, one that was cancelled, one
|
|
620
|
+
that never suspended, and an unknown id each get their own refusal — as does
|
|
621
|
+
an unreadable store, which is refused rather than guessed at.
|
|
622
|
+
- **Idempotent.** A suspension is keyed by run id, so however many operators
|
|
623
|
+
ask there is one resumable pause and no extra traversal — the verb re-arms
|
|
624
|
+
and stops. Two racing callers in one process get one restore and one refusal.
|
|
625
|
+
- **It leaves a trace.** The restore is logged with the run, flow, node, when
|
|
626
|
+
the suspension was consumed, who asked and why, and the run is recorded
|
|
627
|
+
`paused` again so the repair is not invisible. Across a restart the exit
|
|
628
|
+
still works: the consumed suspension rides the run's own terminal history row
|
|
629
|
+
(in `sys_automation_run` columns that already existed), and a run that is
|
|
630
|
+
restored and then finishes clears it.
|
|
631
|
+
|
|
632
|
+
⚠️ A repair, not a prevention. The failed attempt's side effects are **not**
|
|
633
|
+
undone and the original resume signal is **not** replayed — the continuation
|
|
634
|
+
must be re-issued. Whether the pause should survive a downstream throw at all
|
|
635
|
+
is a separate, unruled decision (#13937); this change leaves the resume
|
|
636
|
+
ordering, `forgetSuspendedRun` and `traverseNext` exactly as they are, mints no
|
|
637
|
+
new run status, and works whichever way that is ruled — the runs already stuck
|
|
638
|
+
today are not released by changing what future resumes do.
|
|
639
|
+
|
|
640
|
+
`plugin-approvals` carries a comment correction only: its organization backfill
|
|
641
|
+
documented `context_json` as never written on terminal rows, which this change
|
|
642
|
+
makes false for that one class of row. No behaviour change there.
|
|
643
|
+
- 5a916c4: **Ops:** a one-off, idempotent backfill for the platform rows the pre-#10101 writers stranded with no organization — dry run first (#11308).
|
|
644
|
+
|
|
645
|
+
#10101 fixed the WRITERS: a `sys_approval_request` and a `sys_automation_run` are now stamped from the SUBJECT record's organization, with the acting context as the ruled fallback. It wrote nothing to existing rows, so the population produced before it persists — a **pending** org-less approval request LOCKS the record it is about while being invisible in every organization-scoped inbox, its own owner's included, and automation-run history stays unattributed. This is the repair for those rows, on the maintainer's 2026-08-23 ruling (direction 3).
|
|
646
|
+
|
|
647
|
+
`packages/plugins/plugin-approvals/src/backfill-platform-row-organizations.ts` sweeps `sys_approval_request` (with its `sys_approval_action` / `sys_approval_approver` children, which move with their request) and `sys_automation_run`. It scans only rows whose organization column is unset, re-reads each row's subject at repair time — live record first, the write-time snapshot (`payload_json` / `context_json`'s `record`) second for a subject that has since been deleted — and stamps the platform row with the subject's own organization.
|
|
648
|
+
|
|
649
|
+
**Dry run first, and by default.** `planPlatformRowOrganizationBackfill(engine)` reads only and returns a per-object report naming every row it would touch; `runPlatformRowOrganizationBackfill(engine, { dryRun: false })` writes. Nothing runs at boot and nothing is scheduled: this is an operator-invoked module, run once against an affected install.
|
|
650
|
+
|
|
651
|
+
**Rows whose subject is equally org-less are counted and named, never written.** The acting-context fallback the writers apply is not available to a repair — the acting context is gone — and inventing one stays vetoed. Those ids are reported so the population is checkable and stays visible.
|
|
652
|
+
|
|
653
|
+
**`sys_api_key`'s divergence is preserved, not flattened.** Both the column read on a subject and the column written on a platform row are resolved from the registered schema through the shared `createRecordOrganizationResolver` (`@objectstack/metadata-core`), so a platform row ABOUT an API key is repaired from that object's stamp-only `active_organization_id` (limb 0, #8778) and the credential table itself is never written to.
|
|
654
|
+
|
|
655
|
+
**Idempotent, and asserted rather than claimed.** Every scan is `WHERE <organization column> IS NULL` and every write fills that column, so a repaired row cannot match again; the test suite runs the sweep twice and pins the second run at zero writes.
|
|
656
|
+
|
|
657
|
+
Publishes no runtime code: the module is not exported from the package index and not bundled into `dist` (`tsup` builds `src/index.ts`). It is graded rather than skipped because the release notes are where an operator of an affected install learns the repair exists, what it will and will not touch, and that the dry run comes first.
|
|
658
|
+
- 30928a6: fix(i18n): read the provenance companion at serving time, not only record it (#12642)
|
|
659
|
+
|
|
660
|
+
Maintainer ruling #12069 Option A (#11671) landed translation provenance as
|
|
661
|
+
**two** halves: `os i18n extract --source-hashes` RECORDS which source revision
|
|
662
|
+
a generated leaf is still a byte copy of, and `withSourceFallback` READS those
|
|
663
|
+
records at serving time and substitutes the current source for a leaf whose
|
|
664
|
+
source has moved underneath it. The recording half was then rolled out to every
|
|
665
|
+
bundle set. The reading half was not — measured on `main`: provenance
|
|
666
|
+
**recorded in 9 of 9** bundle sets and **read at serving time in 1**.
|
|
667
|
+
|
|
668
|
+
The other eight assembled their `TranslationBundle` straight from the raw
|
|
669
|
+
generated modules and never consulted the companion sitting beside them, so
|
|
670
|
+
they recorded the drift and went on serving the superseded draft. Nothing said
|
|
671
|
+
so: `check:i18n` compares key sets and they still matched, `check:i18n-coverage`
|
|
672
|
+
counts a present leaf as translated, and `check:i18n-stale-fill`'s cross-locale
|
|
673
|
+
rule needs a SECOND locale holding the same stale bytes before it can testify.
|
|
674
|
+
The measured case had one locale and no second witness.
|
|
675
|
+
|
|
676
|
+
All eight are wired here, in the shape `@objectstack/platform-objects`'s own
|
|
677
|
+
`metadata-translations/index.ts` uses — the committed
|
|
678
|
+
`<locale>.source-hashes.generated.ts` passed as the fourth argument, the third
|
|
679
|
+
left `undefined` because these sets have no hand-authored sections. Provenance
|
|
680
|
+
is now recorded in 9 of 9 sets and served in 9 of 9.
|
|
681
|
+
|
|
682
|
+
`@objectstack/plugin-webhooks` was the last of them and is the only one whose
|
|
683
|
+
manifest changed: `withSourceFallback` lives in `@objectstack/platform-objects`,
|
|
684
|
+
which that package did not declare. It was **already in that package's install
|
|
685
|
+
closure** through `@objectstack/service-messaging`, so the edge declares a
|
|
686
|
+
resolution that already resolved rather than adding a package to the graph —
|
|
687
|
+
and relying on it undeclared would have been a phantom dependency under this
|
|
688
|
+
repo's strict package manager.
|
|
689
|
+
|
|
690
|
+
`check:i18n-stale-fill` gains a second verdict, **UNSERVED PROVENANCE**, so this
|
|
691
|
+
cannot silently come apart again: a bundle set that commits a companion and
|
|
692
|
+
does not consult it at serving time now fails the build, including a tenth set
|
|
693
|
+
that lands tomorrow.
|
|
694
|
+
|
|
695
|
+
**Graded `patch`, and the grade is the interesting part.** No API changes, no
|
|
696
|
+
new exported surface, and no key set moves — substitution was chosen over
|
|
697
|
+
deletion precisely so key-set claims stay put (ruling #8765 Option B). What
|
|
698
|
+
changes is which STRING a stale leaf serves. On this tree that is **zero
|
|
699
|
+
leaves**: a record is only ever written for a leaf that IS a byte copy of the
|
|
700
|
+
current source, so the companions arrive 0-stale by construction. The change is
|
|
701
|
+
in what happens the next time a source string moves — the reader sees the
|
|
702
|
+
English source rather than a superseded draft of it, which is the same
|
|
703
|
+
degradation an untranslated key already produces and not a new state.
|
|
704
|
+
- de47336: chore(i18n): roll the generated-leaf provenance companion out to the remaining bundle sets (#12559)
|
|
705
|
+
|
|
706
|
+
`os i18n extract --source-hashes` (#11671, maintainer ruling #12069 Option A)
|
|
707
|
+
records, per generated translation leaf, the digest of the source revision that
|
|
708
|
+
leaf is **still a byte copy of** — the one signal that tells a stale fill from a
|
|
709
|
+
real translation once the source has moved and the two stopped being
|
|
710
|
+
distinguishable by value. It shipped opt-in, and exactly one of the nine i18n
|
|
711
|
+
bundle sets opted in. A landed detector, a changeset announcing it and a green
|
|
712
|
+
gate read together as *"generated translation staleness is now caught"*; for
|
|
713
|
+
eight of nine sets it was not, and the thing making it not caught was a single
|
|
714
|
+
absent flag in an extract config — invisible from all three of those surfaces.
|
|
715
|
+
|
|
716
|
+
**All eight remaining sets now opt in** — `plugin-approvals`, `plugin-audit`,
|
|
717
|
+
`plugin-security`, `plugin-sharing`, `plugin-webhooks`, `service-messaging`,
|
|
718
|
+
`service-realtime`, `service-storage`. Each documents `source-hashes` in its
|
|
719
|
+
extract config and commits three `<locale>.source-hashes.generated.ts`
|
|
720
|
+
companions, produced by the same extract run as the bundles they sit beside
|
|
721
|
+
(`check:i18n` compares them byte-for-byte, so they cannot be written by hand).
|
|
722
|
+
`check:i18n` now reports 7 bundles per set where it reported 4, and 11 for
|
|
723
|
+
`platform-objects` where it reported 8.
|
|
724
|
+
|
|
725
|
+
**Records count what is currently RECORDABLE, never what is covered.** A record
|
|
726
|
+
is written only for a leaf that *is* right now a byte copy of the current
|
|
727
|
+
source, so a fully translated locale starts with an empty table — which is the
|
|
728
|
+
instrument armed, not an instrument that measures nothing: the entry appears by
|
|
729
|
+
itself on the first extract after a leaf becomes a fill. Measured at this
|
|
730
|
+
commit, per set over its three translated locales: `service-messaging` 289,
|
|
731
|
+
`plugin-approvals` 61, `plugin-security` 33, `plugin-webhooks` 20,
|
|
732
|
+
`plugin-audit` 8, `service-storage` 7, `plugin-sharing` 1 (es-ES only; zh-CN and
|
|
733
|
+
ja-JP are fully translated and start empty), `service-realtime` 0 (all three
|
|
734
|
+
locales fully translated). **419 records written across the eight sets, 0
|
|
735
|
+
stale.**
|
|
736
|
+
|
|
737
|
+
**One extractor fix the rollout forced.** `--source-hashes` had one user, and
|
|
738
|
+
that user commits both generated sections, so the interaction with
|
|
739
|
+
`--no-metadata-forms` had never been exercised. The provenance table is computed
|
|
740
|
+
over every generated section the extractor builds; the eight sets here commit no
|
|
741
|
+
metadata-forms bundle, and their `metadataForms` subtree — absent from their
|
|
742
|
+
merge baseline — arrives as a fresh `--fill=default` copy of `en`, so every leaf
|
|
743
|
+
of it was recordable. First measured on `plugin-audit`: **763 records, of which
|
|
744
|
+
2 were its own objects and 761 were digests of the Studio metadata-form baseline
|
|
745
|
+
`@objectstack/platform-objects` owns.** Those records are unreadable in the
|
|
746
|
+
package holding them and would have rewritten all 24 companions on any unrelated
|
|
747
|
+
`*.form.ts` change in `packages/spec` — the cross-package coupling ADR-0029 D8
|
|
748
|
+
and every `bundle-ownership.test.ts` keep out of committed bundles. The
|
|
749
|
+
companion now covers exactly the sections a run commits, decided by the same two
|
|
750
|
+
predicates that decide the bundle files. `platform-objects` commits both, so its
|
|
751
|
+
three committed companions are byte-for-byte unchanged.
|
|
752
|
+
|
|
753
|
+
**Grade: `patch`, and behaviour on the day it lands is unchanged for every
|
|
754
|
+
leaf.** A record is written only where a leaf is currently a byte copy of the
|
|
755
|
+
**current** source, so every record written equals the current digest and none
|
|
756
|
+
of them can be stale; the mechanism cannot arrive red. No committed translation
|
|
757
|
+
bundle changed a byte, no public API moved, and no leaf's rendered text changed.
|
|
758
|
+
`narrowToCommittedSections` is new but internal to `@objectstack/cli` — the
|
|
759
|
+
package's entrypoint does not re-export the extractor utils.
|
|
760
|
+
|
|
761
|
+
**What this does not do**, stated so the boundary is not inferred wrongly a
|
|
762
|
+
second time: these eight sets now *record* provenance. Reading it at serving
|
|
763
|
+
time is `withSourceFallback`, and that is still wired in
|
|
764
|
+
`@objectstack/platform-objects` alone — so a stale fill in one of the eight is
|
|
765
|
+
now recorded and reportable, but not yet substituted at runtime. Tracked
|
|
766
|
+
separately.
|
|
767
|
+
- ad54eb3: feat(tooling): onboard all 14 `packages/plugins/**` packages into `check:test-typecheck` (#14062)
|
|
768
|
+
|
|
769
|
+
Every plugin package now has a `tsconfig.test.json` compiled by the shared
|
|
770
|
+
`check:test-typecheck` gate, and its `typecheck` script names it. Before this,
|
|
771
|
+
the shrink-only `test-typecheck-debt.json` ratchet said **nothing** about a
|
|
772
|
+
third of the repo's runtime surface: 14 packages, 1 `tsconfig.test.json`
|
|
773
|
+
(`plugin-security`, wired directly to `tsc` rather than to the instrument), and
|
|
774
|
+
0 `check:test-typecheck` scripts.
|
|
775
|
+
|
|
776
|
+
Onboarded as a family by the director ruling of 2026-09-01 on #14062
|
|
777
|
+
(maintainer verbatim: 「同意」), which also carries the #5286 maintainer
|
|
778
|
+
authority the starting ledgers need. The smaller branch triage recommended —
|
|
779
|
+
declare the instrument's scope and re-site the two compile-time pins — was
|
|
780
|
+
recorded as considered and not taken: an instrument silent over a third of the
|
|
781
|
+
runtime surface is a hole readers generalise across, and that costs more than
|
|
782
|
+
fourteen tsconfigs.
|
|
783
|
+
|
|
784
|
+
**Measured, not assumed** (at `e80889095`, workspace closure built first). Four
|
|
785
|
+
packages carry residue and therefore a starting ledger — plugin-approvals 324
|
|
786
|
+
over 8 files, plugin-auth 94 over 10, plugin-sharing 3 over 2,
|
|
787
|
+
knowledge-ragflow 3 over 1. The other ten measure **zero** and deliberately get
|
|
788
|
+
no ledger file at all: the gate reads a missing ledger as `{ entries: {} }`, so
|
|
789
|
+
any error there is red immediately with no entry to be added to — strictly
|
|
790
|
+
stronger than a ledger holding nothing, and the call `plugin-security` had
|
|
791
|
+
already recorded for itself.
|
|
792
|
+
|
|
793
|
+
⛔ **This does not repair 345 type errors.** Per ruling item 3 it makes the
|
|
794
|
+
ratchet able to *see* them; paydown follows the ratchet's own shrink-only
|
|
795
|
+
discipline on its own cards. No test file is edited here.
|
|
796
|
+
|
|
797
|
+
Two corrections to the finding's own prose, both measured: the exclusion is
|
|
798
|
+
narrower than "no plugin package compiles its tests" — 9 of the 14 already
|
|
799
|
+
compiled their tests inside the `typecheck`-invoked build config, at zero
|
|
800
|
+
errors — and `exec-context-annotation.pin.ts` is a `.pin.ts`, which
|
|
801
|
+
`**/*.test.ts` never excluded, so its directives were already live. The pin
|
|
802
|
+
this change genuinely makes real is
|
|
803
|
+
`plugin-approvals/src/manager-org-screen-parity.contract.test.ts`, which no tsc
|
|
804
|
+
program had ever read.
|
|
805
|
+
- 1272f0a: Promote `resolveRecordOrganizationField` to the shared platform-row organization resolver (the cloud#1395 Option A ruling): a platform row's organization is the SUBJECT record's organization; actor context is the fallback, never the primary.
|
|
806
|
+
|
|
807
|
+
- `@objectstack/metadata-core` now owns the resolver (`resolveRecordOrganizationField`, `createFieldPresenceProbe`, and the new memoized `createRecordOrganizationResolver` factory) so all three sanctioned writers share one precedence.
|
|
808
|
+
- `@objectstack/plugin-approvals`: `openNodeRequest` stamps `sys_approval_request`, `sys_approval_action` and the `sys_approval_approver` index from the subject record's organization (acting context as fallback). Fixes the measured defect where every schedule / time-relative / api triggered approval persisted `organization_id = NULL` — locking the record it was about while being invisible in every inbox, its owner's included.
|
|
809
|
+
- `@objectstack/service-automation`: `sys_automation_run` rows (paused and terminal) resolve their organization from the trigger-record snapshot, with the acting tenant as fallback. Terminal rows previously never carried an organization at all.
|
|
810
|
+
- `@objectstack/plugin-audit`: the resolver moved out; the package re-exports it from the original paths, behavior unchanged.
|
|
811
|
+
|
|
812
|
+
The `sys_api_key` divergence is preserved and pinned: `tenancy.organizationField` (who a row is ABOUT) still wins over the tenant wall answer, and the credential table stays unwalled.
|
|
813
|
+
- Updated dependencies [809d417]
|
|
814
|
+
- Updated dependencies [387e231]
|
|
815
|
+
- Updated dependencies [f794e4e]
|
|
816
|
+
- Updated dependencies [cae2169]
|
|
817
|
+
- Updated dependencies [b812a54]
|
|
818
|
+
- Updated dependencies [2d4fa75]
|
|
819
|
+
- Updated dependencies [0e4e51b]
|
|
820
|
+
- Updated dependencies [e84bbf6]
|
|
821
|
+
- Updated dependencies [effae80]
|
|
822
|
+
- Updated dependencies [efb3513]
|
|
823
|
+
- Updated dependencies [d62f990]
|
|
824
|
+
- Updated dependencies [c45d8e6]
|
|
825
|
+
- Updated dependencies [2e3e8c7]
|
|
826
|
+
- Updated dependencies [e621291]
|
|
827
|
+
- Updated dependencies [655b106]
|
|
828
|
+
- Updated dependencies [40a93b5]
|
|
829
|
+
- Updated dependencies [101ad2c]
|
|
830
|
+
- Updated dependencies [d5b330d]
|
|
831
|
+
- Updated dependencies [dda969c]
|
|
832
|
+
- Updated dependencies [1f45690]
|
|
833
|
+
- Updated dependencies [277948f]
|
|
834
|
+
- Updated dependencies [8bdd955]
|
|
835
|
+
- Updated dependencies [54e2d36]
|
|
836
|
+
- Updated dependencies [b745157]
|
|
837
|
+
- Updated dependencies [f3bbbef]
|
|
838
|
+
- Updated dependencies [4f24e9d]
|
|
839
|
+
- Updated dependencies [e27583e]
|
|
840
|
+
- Updated dependencies [4bd6faa]
|
|
841
|
+
- Updated dependencies [86cbe37]
|
|
842
|
+
- Updated dependencies [6a180e4]
|
|
843
|
+
- Updated dependencies [474242f]
|
|
844
|
+
- Updated dependencies [63cd487]
|
|
845
|
+
- Updated dependencies [bd4aa4e]
|
|
846
|
+
- Updated dependencies [803eaab]
|
|
847
|
+
- Updated dependencies [f8e8f03]
|
|
848
|
+
- Updated dependencies [983edf1]
|
|
849
|
+
- Updated dependencies [eae824e]
|
|
850
|
+
- Updated dependencies [f6fa22c]
|
|
851
|
+
- Updated dependencies [8a483b3]
|
|
852
|
+
- Updated dependencies [3bc2e38]
|
|
853
|
+
- Updated dependencies [97bcd99]
|
|
854
|
+
- Updated dependencies [df59de0]
|
|
855
|
+
- Updated dependencies [96e25a8]
|
|
856
|
+
- Updated dependencies [713f83f]
|
|
857
|
+
- Updated dependencies [77d4b3c]
|
|
858
|
+
- Updated dependencies [f75a38a]
|
|
859
|
+
- Updated dependencies [7a25e7d]
|
|
860
|
+
- Updated dependencies [1fa05a6]
|
|
861
|
+
- Updated dependencies [c85a265]
|
|
862
|
+
- Updated dependencies [dcb10a5]
|
|
863
|
+
- Updated dependencies [773a999]
|
|
864
|
+
- Updated dependencies [35dffea]
|
|
865
|
+
- Updated dependencies [d8024f0]
|
|
866
|
+
- Updated dependencies [8120808]
|
|
867
|
+
- Updated dependencies [776a098]
|
|
868
|
+
- Updated dependencies [5060877]
|
|
869
|
+
- Updated dependencies [4f6325d]
|
|
870
|
+
- Updated dependencies [52954c0]
|
|
871
|
+
- Updated dependencies [2aa8456]
|
|
872
|
+
- Updated dependencies [d23ebb9]
|
|
873
|
+
- Updated dependencies [93809a3]
|
|
874
|
+
- Updated dependencies [7c0d0c3]
|
|
875
|
+
- Updated dependencies [daae7aa]
|
|
876
|
+
- Updated dependencies [8dc22d6]
|
|
877
|
+
- Updated dependencies [fa5d137]
|
|
878
|
+
- Updated dependencies [a392dbf]
|
|
879
|
+
- Updated dependencies [279431e]
|
|
880
|
+
- Updated dependencies [948dd6b]
|
|
881
|
+
- Updated dependencies [3b4c56c]
|
|
882
|
+
- Updated dependencies [ae8edd2]
|
|
883
|
+
- Updated dependencies [e25403c]
|
|
884
|
+
- Updated dependencies [a81aa9d]
|
|
885
|
+
- Updated dependencies [64baa68]
|
|
886
|
+
- Updated dependencies [9fa70d7]
|
|
887
|
+
- Updated dependencies [09db64a]
|
|
888
|
+
- Updated dependencies [92916e7]
|
|
889
|
+
- Updated dependencies [a84f3ea]
|
|
890
|
+
- Updated dependencies [f2eaae8]
|
|
891
|
+
- Updated dependencies [56c093c]
|
|
892
|
+
- Updated dependencies [c09451b]
|
|
893
|
+
- Updated dependencies [ba64877]
|
|
894
|
+
- Updated dependencies [e7191ce]
|
|
895
|
+
- Updated dependencies [7345308]
|
|
896
|
+
- Updated dependencies [79b6a22]
|
|
897
|
+
- Updated dependencies [30d96ab]
|
|
898
|
+
- Updated dependencies [f658793]
|
|
899
|
+
- Updated dependencies [0fd4899]
|
|
900
|
+
- Updated dependencies [c95ad19]
|
|
901
|
+
- Updated dependencies [e58ea8b]
|
|
902
|
+
- Updated dependencies [4a17645]
|
|
903
|
+
- Updated dependencies [3795c5f]
|
|
904
|
+
- Updated dependencies [8ab926b]
|
|
905
|
+
- Updated dependencies [7317cf2]
|
|
906
|
+
- Updated dependencies [e25e839]
|
|
907
|
+
- Updated dependencies [5997207]
|
|
908
|
+
- Updated dependencies [8b13cc8]
|
|
909
|
+
- Updated dependencies [00d8f65]
|
|
910
|
+
- Updated dependencies [4a4a35d]
|
|
911
|
+
- Updated dependencies [4a4a35d]
|
|
912
|
+
- Updated dependencies [86e765a]
|
|
913
|
+
- Updated dependencies [1d7e76a]
|
|
914
|
+
- Updated dependencies [53dc739]
|
|
915
|
+
- Updated dependencies [fd289be]
|
|
916
|
+
- Updated dependencies [03bf7b1]
|
|
917
|
+
- Updated dependencies [f90e820]
|
|
918
|
+
- Updated dependencies [18d816a]
|
|
919
|
+
- Updated dependencies [e8bd715]
|
|
920
|
+
- Updated dependencies [b91c351]
|
|
921
|
+
- Updated dependencies [a28a3c0]
|
|
922
|
+
- Updated dependencies [200d255]
|
|
923
|
+
- Updated dependencies [2852acc]
|
|
924
|
+
- Updated dependencies [daeaaf9]
|
|
925
|
+
- Updated dependencies [c459da6]
|
|
926
|
+
- Updated dependencies [e914733]
|
|
927
|
+
- Updated dependencies [1d8ad0f]
|
|
928
|
+
- Updated dependencies [9738c35]
|
|
929
|
+
- Updated dependencies [f887e52]
|
|
930
|
+
- Updated dependencies [881f8d8]
|
|
931
|
+
- Updated dependencies [3bfa1e6]
|
|
932
|
+
- Updated dependencies [0a8ebf3]
|
|
933
|
+
- Updated dependencies [901355c]
|
|
934
|
+
- Updated dependencies [34ce8e7]
|
|
935
|
+
- Updated dependencies [33681ea]
|
|
936
|
+
- Updated dependencies [bfe13c8]
|
|
937
|
+
- Updated dependencies [0fb3044]
|
|
938
|
+
- Updated dependencies [4635f3e]
|
|
939
|
+
- Updated dependencies [fd289be]
|
|
940
|
+
- Updated dependencies [ee3595c]
|
|
941
|
+
- Updated dependencies [09b4f4e]
|
|
942
|
+
- Updated dependencies [b2eab95]
|
|
943
|
+
- Updated dependencies [93940d4]
|
|
944
|
+
- Updated dependencies [3a04b01]
|
|
945
|
+
- Updated dependencies [45b9051]
|
|
946
|
+
- Updated dependencies [3954fb7]
|
|
947
|
+
- Updated dependencies [4805b56]
|
|
948
|
+
- Updated dependencies [b9e9227]
|
|
949
|
+
- Updated dependencies [d395692]
|
|
950
|
+
- Updated dependencies [5894d30]
|
|
951
|
+
- Updated dependencies [a3765f6]
|
|
952
|
+
- Updated dependencies [2d5cee3]
|
|
953
|
+
- Updated dependencies [e22158f]
|
|
954
|
+
- Updated dependencies [7404925]
|
|
955
|
+
- Updated dependencies [0c2334f]
|
|
956
|
+
- Updated dependencies [778c59f]
|
|
957
|
+
- Updated dependencies [d2619fd]
|
|
958
|
+
- Updated dependencies [af56546]
|
|
959
|
+
- Updated dependencies [6acb11a]
|
|
960
|
+
- Updated dependencies [33c5fd3]
|
|
961
|
+
- Updated dependencies [20b0fdb]
|
|
962
|
+
- Updated dependencies [905019b]
|
|
963
|
+
- Updated dependencies [a286411]
|
|
964
|
+
- Updated dependencies [98c0d33]
|
|
965
|
+
- Updated dependencies [368a82e]
|
|
966
|
+
- Updated dependencies [a3d5724]
|
|
967
|
+
- Updated dependencies [93ea19b]
|
|
968
|
+
- Updated dependencies [9ee2dcf]
|
|
969
|
+
- Updated dependencies [8cb96ec]
|
|
970
|
+
- Updated dependencies [8f10a79]
|
|
971
|
+
- Updated dependencies [6269a55]
|
|
972
|
+
- Updated dependencies [a17da05]
|
|
973
|
+
- Updated dependencies [a8c00e2]
|
|
974
|
+
- Updated dependencies [22e5236]
|
|
975
|
+
- Updated dependencies [0fb8760]
|
|
976
|
+
- Updated dependencies [e5ce2ed]
|
|
977
|
+
- Updated dependencies [be21955]
|
|
978
|
+
- Updated dependencies [bc56e18]
|
|
979
|
+
- Updated dependencies [be21955]
|
|
980
|
+
- Updated dependencies [a9ee989]
|
|
981
|
+
- Updated dependencies [4d0d944]
|
|
982
|
+
- Updated dependencies [15d58db]
|
|
983
|
+
- Updated dependencies [d63b014]
|
|
984
|
+
- Updated dependencies [9abe4e4]
|
|
985
|
+
- Updated dependencies [2cc7122]
|
|
986
|
+
- Updated dependencies [50d6c92]
|
|
987
|
+
- Updated dependencies [15d55fb]
|
|
988
|
+
- Updated dependencies [9e0ba21]
|
|
989
|
+
- Updated dependencies [311433f]
|
|
990
|
+
- Updated dependencies [3e5ad08]
|
|
991
|
+
- Updated dependencies [9abe4e4]
|
|
992
|
+
- Updated dependencies [b7131f3]
|
|
993
|
+
- Updated dependencies [e5812fa]
|
|
994
|
+
- Updated dependencies [7085f90]
|
|
995
|
+
- Updated dependencies [dee4dd4]
|
|
996
|
+
- Updated dependencies [ce7e497]
|
|
997
|
+
- Updated dependencies [51ecb2f]
|
|
998
|
+
- Updated dependencies [9086761]
|
|
999
|
+
- Updated dependencies [f6344e7]
|
|
1000
|
+
- Updated dependencies [42a117b]
|
|
1001
|
+
- Updated dependencies [1401ae7]
|
|
1002
|
+
- Updated dependencies [4297fe7]
|
|
1003
|
+
- Updated dependencies [e398863]
|
|
1004
|
+
- Updated dependencies [d16df74]
|
|
1005
|
+
- Updated dependencies [d79c602]
|
|
1006
|
+
- Updated dependencies [f11fc61]
|
|
1007
|
+
- Updated dependencies [e808890]
|
|
1008
|
+
- Updated dependencies [8f79379]
|
|
1009
|
+
- Updated dependencies [e6ca40e]
|
|
1010
|
+
- Updated dependencies [0c77ea4]
|
|
1011
|
+
- Updated dependencies [52954c0]
|
|
1012
|
+
- Updated dependencies [89eb997]
|
|
1013
|
+
- Updated dependencies [7131f12]
|
|
1014
|
+
- Updated dependencies [aa5994e]
|
|
1015
|
+
- Updated dependencies [be93457]
|
|
1016
|
+
- Updated dependencies [a65db76]
|
|
1017
|
+
- Updated dependencies [2cf5a96]
|
|
1018
|
+
- Updated dependencies [15eb2c9]
|
|
1019
|
+
- Updated dependencies [5691b07]
|
|
1020
|
+
- Updated dependencies [2a6122b]
|
|
1021
|
+
- Updated dependencies [225e769]
|
|
1022
|
+
- Updated dependencies [8af88dd]
|
|
1023
|
+
- Updated dependencies [fb5fbb8]
|
|
1024
|
+
- Updated dependencies [d7b3963]
|
|
1025
|
+
- Updated dependencies [33184fd]
|
|
1026
|
+
- Updated dependencies [7c41693]
|
|
1027
|
+
- Updated dependencies [b72db01]
|
|
1028
|
+
- Updated dependencies [dce5cd4]
|
|
1029
|
+
- Updated dependencies [9688f58]
|
|
1030
|
+
- Updated dependencies [556ebc1]
|
|
1031
|
+
- Updated dependencies [177ebdc]
|
|
1032
|
+
- Updated dependencies [8d237b4]
|
|
1033
|
+
- Updated dependencies [2d2e6f0]
|
|
1034
|
+
- Updated dependencies [2d8dd8d]
|
|
1035
|
+
- Updated dependencies [22d573e]
|
|
1036
|
+
- Updated dependencies [b5a2398]
|
|
1037
|
+
- Updated dependencies [348860c]
|
|
1038
|
+
- Updated dependencies [5383fa6]
|
|
1039
|
+
- Updated dependencies [5b3ff63]
|
|
1040
|
+
- Updated dependencies [1a6a19c]
|
|
1041
|
+
- Updated dependencies [064d484]
|
|
1042
|
+
- Updated dependencies [527e050]
|
|
1043
|
+
- Updated dependencies [dd33bf9]
|
|
1044
|
+
- Updated dependencies [4cb2a90]
|
|
1045
|
+
- Updated dependencies [74a7804]
|
|
1046
|
+
- Updated dependencies [53d3689]
|
|
1047
|
+
- Updated dependencies [b3a63d3]
|
|
1048
|
+
- Updated dependencies [49f0dcf]
|
|
1049
|
+
- Updated dependencies [033a34c]
|
|
1050
|
+
- Updated dependencies [4d25d22]
|
|
1051
|
+
- Updated dependencies [1ffee51]
|
|
1052
|
+
- Updated dependencies [5ae4303]
|
|
1053
|
+
- Updated dependencies [ece4dad]
|
|
1054
|
+
- Updated dependencies [e9b377e]
|
|
1055
|
+
- Updated dependencies [146f448]
|
|
1056
|
+
- Updated dependencies [735f5c7]
|
|
1057
|
+
- Updated dependencies [a7e18de]
|
|
1058
|
+
- Updated dependencies [366f895]
|
|
1059
|
+
- Updated dependencies [dc75ba8]
|
|
1060
|
+
- Updated dependencies [cce0aa9]
|
|
1061
|
+
- Updated dependencies [e764507]
|
|
1062
|
+
- Updated dependencies [cff17af]
|
|
1063
|
+
- Updated dependencies [39404f3]
|
|
1064
|
+
- Updated dependencies [ca1965f]
|
|
1065
|
+
- Updated dependencies [8619f95]
|
|
1066
|
+
- Updated dependencies [b706af9]
|
|
1067
|
+
- Updated dependencies [db8c288]
|
|
1068
|
+
- Updated dependencies [0e5fe7f]
|
|
1069
|
+
- Updated dependencies [add4360]
|
|
1070
|
+
- Updated dependencies [e0abc38]
|
|
1071
|
+
- Updated dependencies [fc9ba76]
|
|
1072
|
+
- Updated dependencies [1272f0a]
|
|
1073
|
+
- Updated dependencies [0f94cc7]
|
|
1074
|
+
- Updated dependencies [a11c1a5]
|
|
1075
|
+
- Updated dependencies [71f9cd1]
|
|
1076
|
+
- Updated dependencies [ee17d86]
|
|
1077
|
+
- Updated dependencies [cdbd920]
|
|
1078
|
+
- Updated dependencies [18c432e]
|
|
1079
|
+
- Updated dependencies [3c418c4]
|
|
1080
|
+
- Updated dependencies [fa8715a]
|
|
1081
|
+
- Updated dependencies [a933ed7]
|
|
1082
|
+
- Updated dependencies [b3ca463]
|
|
1083
|
+
- Updated dependencies [a933ed7]
|
|
1084
|
+
- Updated dependencies [0d4a6a8]
|
|
1085
|
+
- Updated dependencies [518d5e5]
|
|
1086
|
+
- Updated dependencies [6643ba1]
|
|
1087
|
+
- Updated dependencies [eeba2ef]
|
|
1088
|
+
- Updated dependencies [ec4c4d2]
|
|
1089
|
+
- Updated dependencies [424f73c]
|
|
1090
|
+
- Updated dependencies [cccbe51]
|
|
1091
|
+
- Updated dependencies [a8d6b1d]
|
|
1092
|
+
- Updated dependencies [e4a7695]
|
|
1093
|
+
- Updated dependencies [87075b1]
|
|
1094
|
+
- Updated dependencies [fc58a99]
|
|
1095
|
+
- Updated dependencies [14cfc00]
|
|
1096
|
+
- Updated dependencies [1c6f7b4]
|
|
1097
|
+
- Updated dependencies [e854a53]
|
|
1098
|
+
- Updated dependencies [dfebfc8]
|
|
1099
|
+
- Updated dependencies [598b7ec]
|
|
1100
|
+
- Updated dependencies [d028b37]
|
|
1101
|
+
- Updated dependencies [f7b25c5]
|
|
1102
|
+
- Updated dependencies [122ef38]
|
|
1103
|
+
- Updated dependencies [4a37870]
|
|
1104
|
+
- Updated dependencies [428f9b2]
|
|
1105
|
+
- Updated dependencies [aa7ff56]
|
|
1106
|
+
- Updated dependencies [811a3c2]
|
|
1107
|
+
- Updated dependencies [1401ae7]
|
|
1108
|
+
- Updated dependencies [2fd3f1c]
|
|
1109
|
+
- Updated dependencies [c41b42e]
|
|
1110
|
+
- Updated dependencies [d41d166]
|
|
1111
|
+
- Updated dependencies [c4db311]
|
|
1112
|
+
- Updated dependencies [750fff5]
|
|
1113
|
+
- Updated dependencies [c19035e]
|
|
1114
|
+
- Updated dependencies [ececf7a]
|
|
1115
|
+
- Updated dependencies [d173125]
|
|
1116
|
+
- Updated dependencies [8eeca27]
|
|
1117
|
+
- Updated dependencies [8425c17]
|
|
1118
|
+
- Updated dependencies [a5ef1d8]
|
|
1119
|
+
- Updated dependencies [87ad30c]
|
|
1120
|
+
- Updated dependencies [772d5de]
|
|
1121
|
+
- Updated dependencies [ce80ec2]
|
|
1122
|
+
- Updated dependencies [b372318]
|
|
1123
|
+
- Updated dependencies [97a2263]
|
|
1124
|
+
- Updated dependencies [29d0676]
|
|
1125
|
+
- Updated dependencies [0169d49]
|
|
1126
|
+
- Updated dependencies [6bd3231]
|
|
1127
|
+
- Updated dependencies [d2b5ba8]
|
|
1128
|
+
- Updated dependencies [b799ac5]
|
|
1129
|
+
- Updated dependencies [8f74307]
|
|
1130
|
+
- Updated dependencies [d23dc08]
|
|
1131
|
+
- Updated dependencies [038f333]
|
|
1132
|
+
- Updated dependencies [644ad50]
|
|
1133
|
+
- Updated dependencies [9735662]
|
|
1134
|
+
- Updated dependencies [4d5b4f8]
|
|
1135
|
+
- Updated dependencies [5d16379]
|
|
1136
|
+
- Updated dependencies [0da7cd2]
|
|
1137
|
+
- Updated dependencies [28a5c3e]
|
|
1138
|
+
- Updated dependencies [4bc18e5]
|
|
1139
|
+
- Updated dependencies [9f57f1e]
|
|
1140
|
+
- @objectstack/spec@17.3.0
|
|
1141
|
+
- @objectstack/platform-objects@17.3.0
|
|
1142
|
+
- @objectstack/core@17.3.0
|
|
1143
|
+
- @objectstack/types@17.3.0
|
|
1144
|
+
- @objectstack/metadata-core@17.3.0
|
|
1145
|
+
- @objectstack/formula@17.3.0
|
|
1146
|
+
|
|
3
1147
|
## 17.2.0
|
|
4
1148
|
|
|
5
1149
|
### Minor Changes
|