pi-daddy 0.21.1 → 0.22.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 +20 -1
- package/README.md +12 -7
- package/contracts/ledger/v3/fixtures/capability-decision.json +3 -4
- package/contracts/ledger/v3/fixtures/check-receipt.json +3 -4
- package/contracts/ledger/v3/fixtures/child-lifecycle.json +3 -4
- package/contracts/ledger/v3/fixtures/workflow-fact.json +3 -4
- package/contracts/ledger/v3/fixtures/workspace-lease.json +3 -4
- package/contracts/ledger/v3/ledger-event.schema.json +38 -13
- package/dist/check-runner.d.ts.map +1 -1
- package/dist/check-runner.js +161 -149
- package/dist/check-runner.js.map +1 -1
- package/dist/correlation.d.ts +15 -4
- package/dist/correlation.d.ts.map +1 -1
- package/dist/correlation.js +40 -2
- package/dist/correlation.js.map +1 -1
- package/dist/delegation-approval.d.ts +2 -2
- package/dist/delegation-approval.d.ts.map +1 -1
- package/dist/delegation-approval.js +2 -0
- package/dist/delegation-approval.js.map +1 -1
- package/dist/finalization.d.ts +15 -0
- package/dist/finalization.d.ts.map +1 -0
- package/dist/finalization.js +65 -0
- package/dist/finalization.js.map +1 -0
- package/dist/git-identity.d.ts.map +1 -1
- package/dist/git-identity.js +18 -15
- package/dist/git-identity.js.map +1 -1
- package/dist/grant-store.d.ts +13 -0
- package/dist/grant-store.d.ts.map +1 -1
- package/dist/grant-store.js +48 -25
- package/dist/grant-store.js.map +1 -1
- package/dist/init.d.ts.map +1 -1
- package/dist/init.js +2 -6
- package/dist/init.js.map +1 -1
- package/dist/model-preflight.d.ts +8 -0
- package/dist/model-preflight.d.ts.map +1 -0
- package/dist/model-preflight.js +20 -0
- package/dist/model-preflight.js.map +1 -0
- package/dist/refusals.d.ts +1 -1
- package/dist/refusals.d.ts.map +1 -1
- package/dist/refusals.js +2 -0
- package/dist/refusals.js.map +1 -1
- package/dist/run-herdr.d.ts.map +1 -1
- package/dist/run-herdr.js +6 -5
- package/dist/run-herdr.js.map +1 -1
- package/extensions/chain-plan.ts +16 -0
- package/extensions/correlation-shape.ts +39 -0
- package/extensions/delegate-chain.ts +9 -14
- package/extensions/delegation.ts +2 -24
- package/extensions/execute-child.ts +8 -1
- package/extensions/grant-store-refusal.ts +36 -0
- package/extensions/grants.ts +6 -0
- package/extensions/run-delegation.ts +21 -2
- package/extensions/session.ts +15 -5
- package/extensions/stored-grant-session.ts +33 -0
- package/package.json +3 -1
- package/src/check-runner.ts +56 -44
- package/src/correlation.ts +55 -6
- package/src/delegation-approval.ts +4 -2
- package/src/finalization.ts +79 -0
- package/src/git-identity.ts +20 -16
- package/src/grant-store.ts +55 -25
- package/src/init.ts +5 -5
- package/src/model-preflight.ts +32 -0
- package/src/refusals.ts +2 -0
- package/src/run-herdr.ts +6 -4
package/CHANGELOG.md
CHANGED
|
@@ -12,12 +12,31 @@ the record of how the package got here and are worth keeping; they are not worth
|
|
|
12
12
|
> the record of how the package arrived at what it does, and because the reasoning behind each one is
|
|
13
13
|
> usually the clearest statement of why the current behaviour is what it is.
|
|
14
14
|
|
|
15
|
+
## 0.22.0 — fail-closed audit follow-ups (2026-09-04)
|
|
16
|
+
|
|
17
|
+
- **BREAKING — invalid project stores now fail closed.** Malformed, unsupported, unreadable and wrong-directory
|
|
18
|
+
stores never restore the ungoverned wildcard; `GRANT_STORE_INVALID` records the safety stop. Repair the store
|
|
19
|
+
or deliberately rerun `/grants init` rather than relying on invalid state being treated as absent.
|
|
20
|
+
- **BREAKING — explicit delegation models are resolved before any side effect.** Models absent from pi's session
|
|
21
|
+
catalogue refuse as `MODEL_UNRESOLVED`. Register custom models with pi, or deliberately set the operator
|
|
22
|
+
escape hatch `PI_GRANTS_ALLOW_UNRESOLVED_MODELS=1`.
|
|
23
|
+
- **BREAKING — correlation inputs are narrower.** `schema_version`, when present, must be exactly `"1.0"`, and
|
|
24
|
+
`assurance_scope` must use the closed entire-run/selectors union instead of arbitrary JSON. Strict ledger-v3
|
|
25
|
+
consumers must re-pin to the regenerated schema and fixtures, which also add `GRANT_STORE_INVALID` and
|
|
26
|
+
`MODEL_UNRESOLVED` to the refusal enum.
|
|
27
|
+
- Supplied `tree_sha` and `last_change_seq` now narrow exact approval reuse. `@types/node >=22` is declared as
|
|
28
|
+
a peer because the public declarations use `NodeJS.*`.
|
|
29
|
+
- Primary operation errors are preserved when Herdr, named-check, Git-index, or init cleanup also fails. Every
|
|
30
|
+
finalizer still runs and its failures remain visible; an attached Herdr close failure still retains the
|
|
31
|
+
workspace writer lease.
|
|
32
|
+
- CI runs the model-free, Herdr-free real-pi integration tier.
|
|
33
|
+
|
|
15
34
|
## 0.21.1 — twenty-minute default child timeout (2026-09-02)
|
|
16
35
|
|
|
17
36
|
- Raise the default per-child wall-clock limit from 600 to 1200 seconds. Explicit
|
|
18
37
|
`PI_GRANTS_CHILD_TIMEOUT` values remain unchanged and inherited by descendants.
|
|
19
38
|
|
|
20
|
-
## 0.21.0 — one project init enables its ledger (2026-09-
|
|
39
|
+
## 0.21.0 — one project init enables its ledger (2026-09-02)
|
|
21
40
|
|
|
22
41
|
- `/grants init` now atomically persists one project choice containing both the capability grant and default
|
|
23
42
|
`.pi/grants.jsonl`, then adopts both after that write succeeds. Future plain `pi` starts load both, so
|
package/README.md
CHANGED
|
@@ -141,7 +141,7 @@ A delegator that legitimately holds `fabric_exec` and knowingly wants a child to
|
|
|
141
141
|
that child. `assertNarrowing`'s `allowUniversal` flag exists but is deliberately not plumbed through; the
|
|
142
142
|
first real need for that override is the evidence it should be added.
|
|
143
143
|
|
|
144
|
-
## The
|
|
144
|
+
## The three tools
|
|
145
145
|
|
|
146
146
|
```
|
|
147
147
|
delegate({ task: "summarise src/", agent: "docs-writer" }) // preferred: an operator-authored definition
|
|
@@ -156,7 +156,7 @@ delegate_all({ children: [ {…}, {…}, {…} ] }) // several
|
|
|
156
156
|
- **Spawning is itself a capability.** Grant `delegate` and the child can sub-delegate; withhold it and the
|
|
157
157
|
child is a leaf — the extension is only passed to children that hold it, so the machinery isn't even
|
|
158
158
|
present. `PI_GRANTS_MAX_DEPTH` remains a backstop.
|
|
159
|
-
- **A refusal is a tool *error*, not an answer.**
|
|
159
|
+
- **A refusal is a tool *error*, not an answer.** All three tools throw, because `AgentToolResult` has **no
|
|
160
160
|
`isError` field** — pi sets it only when `execute` throws, and a normal return is hardcoded
|
|
161
161
|
`isError: false`. Until 0.5.0 the tool returned `isError: true`, which was silently discarded, so every
|
|
162
162
|
refusal this package made was recorded by pi as a **successful** tool call. Found by the integration suite
|
|
@@ -593,6 +593,7 @@ everything below it.
|
|
|
593
593
|
| `PI_GRANTS_WORKSPACE_REGISTRY` | unset | Operator-owned `{version:1, workspaces:{id:{path}}}` file, required only for workspace-routed spawns. |
|
|
594
594
|
| `PI_GRANTS_WORKSPACE_LEASE_DIR` | under `$PI_CODING_AGENT_DIR/pi-daddy/` | Kernel writer locks and ownership metadata. |
|
|
595
595
|
| `PI_GRANTS_CHILD_TIMEOUT` | `1200` (seconds) | Wall-clock limit for a child. Inherited by descendants — an operator preference, deliberately *not* attenuating state. |
|
|
596
|
+
| `PI_GRANTS_ALLOW_UNRESOLVED_MODELS` | unset | Exact `1` lets pi attempt custom model resolution; otherwise an explicit provider/id missing from pi's session catalogue refuses before lease, approval or spawn. |
|
|
596
597
|
| `PI_GRANTS_FANOUT` | `8` | Per-call width and downward subtree budget; not a session-total counter. Malformed or `0` falls back to the default. |
|
|
597
598
|
| `PI_GRANTS_PARENT_ID` | `d0` | Readable logical tree position; set by the parent and allowed to repeat across calls. |
|
|
598
599
|
| `PI_GRANTS_EXECUTION_ID` | unset at a root | Unique governed execution occurrence; set by the parent. Lifecycle/lease joins use this, never `PI_GRANTS_PARENT_ID`. |
|
|
@@ -601,7 +602,9 @@ everything below it.
|
|
|
601
602
|
| `PI_GRANTS_HERDR_KEEP_PANE` | unset | `1` keeps each child's pane for inspection, and no sweep closes it. Off by default: a fan-out would flood the workspace. |
|
|
602
603
|
| `PI_CODING_AGENT_DIR` | `~/.pi/agent` | pi's own variable, not ours — but it decides where stored project grants/ledger consent and persisted approvals live. |
|
|
603
604
|
|
|
604
|
-
**A malformed value disables spawning; it never falls back to a default.**
|
|
605
|
+
**A malformed value disables spawning; it never falls back to a default.** Stored grants are likewise
|
|
606
|
+
tri-state: only a missing file is opt-out; malformed, unsupported-version, unreadable or wrong-cwd state is a
|
|
607
|
+
loud empty-grant session with a `GRANT_STORE_INVALID` ledger line. An unreadable
|
|
605
608
|
`PI_GRANTS_MAX_DEPTH` or `PI_GRANTS_DEPTH` yields `maxDepth: 0` and a startup warning naming the
|
|
606
609
|
variable. Before 0.5.0 these were read with `parseInt`, which accepts numeric prefixes (`"2abc"` → `2`)
|
|
607
610
|
and otherwise gives `NaN` — and since every comparison against `NaN` is false, a typo did not tighten the
|
|
@@ -632,8 +635,10 @@ timed out. A pending running append always lands before a terminal event. `denie
|
|
|
632
635
|
is invisible without a record.**
|
|
633
636
|
|
|
634
637
|
Workflow facts are identifier-only and mark `planned`, `observed`, or `controller_validated`; they can never
|
|
635
|
-
claim pi-daddy enforcement. Capability/lifecycle records are the enforced class. Correlation remains
|
|
636
|
-
caller-declared
|
|
638
|
+
claim pi-daddy enforcement. Capability/lifecycle records are the enforced class. Correlation remains
|
|
639
|
+
caller-declared: optional `schema_version` is exactly `1.0`, and `assurance_scope` is either entire-run with an
|
|
640
|
+
empty selector list or selectors with one or more non-empty strings. Fields eligible for display use the v3
|
|
641
|
+
ASCII identifier grammar rather than prose.
|
|
637
642
|
|
|
638
643
|
**Privacy is a property of this file, and the boundary is exact: capability ids, counts and identifiers only
|
|
639
644
|
— never prompts, task text, tool arguments or results.** Trusted `definitionDigest` and `taskDigest` values
|
|
@@ -784,9 +789,9 @@ every in-repo test passed. `npm run test:smoke` packs a tarball, installs it int
|
|
|
784
789
|
## Testing
|
|
785
790
|
|
|
786
791
|
```bash
|
|
787
|
-
npm test #
|
|
792
|
+
npm test # 741 unit tests. Fast, pure, no pi, no network.
|
|
788
793
|
npm run typecheck # src + extensions + tests + integration tests
|
|
789
|
-
npm run test:integration #
|
|
794
|
+
npm run test:integration # 48 tests against a REAL pi process/Herdr server, no model tokens.
|
|
790
795
|
npm run test:smoke # pack/install; exercise library exports, both bins, the v2/v3 contracts,
|
|
791
796
|
# bundled Herdr plugin, dashboard, and `pi-daddy init`
|
|
792
797
|
PI_GRANTS_IT_MODEL=1 npm run test:integration # + 10 end-to-end tests with a real model. Costs money.
|
|
@@ -24,11 +24,10 @@
|
|
|
24
24
|
"policy_label": "policy-contract",
|
|
25
25
|
"assurance_source": "policy",
|
|
26
26
|
"assurance_scope": {
|
|
27
|
-
"
|
|
28
|
-
"
|
|
27
|
+
"type": "selectors",
|
|
28
|
+
"selectors": [
|
|
29
29
|
"src/**"
|
|
30
|
-
]
|
|
31
|
-
"include_untracked": true
|
|
30
|
+
]
|
|
32
31
|
},
|
|
33
32
|
"activated_at": "2026-08-20T12:00:00.000Z",
|
|
34
33
|
"plan_digest": "1111111111111111111111111111111111111111111111111111111111111111",
|
|
@@ -21,11 +21,10 @@
|
|
|
21
21
|
"policy_label": "policy-contract",
|
|
22
22
|
"assurance_source": "policy",
|
|
23
23
|
"assurance_scope": {
|
|
24
|
-
"
|
|
25
|
-
"
|
|
24
|
+
"type": "selectors",
|
|
25
|
+
"selectors": [
|
|
26
26
|
"src/**"
|
|
27
|
-
]
|
|
28
|
-
"include_untracked": true
|
|
27
|
+
]
|
|
29
28
|
},
|
|
30
29
|
"activated_at": "2026-08-20T12:00:00.000Z",
|
|
31
30
|
"plan_digest": "1111111111111111111111111111111111111111111111111111111111111111",
|
|
@@ -24,11 +24,10 @@
|
|
|
24
24
|
"policy_label": "policy-contract",
|
|
25
25
|
"assurance_source": "policy",
|
|
26
26
|
"assurance_scope": {
|
|
27
|
-
"
|
|
28
|
-
"
|
|
27
|
+
"type": "selectors",
|
|
28
|
+
"selectors": [
|
|
29
29
|
"src/**"
|
|
30
|
-
]
|
|
31
|
-
"include_untracked": true
|
|
30
|
+
]
|
|
32
31
|
},
|
|
33
32
|
"activated_at": "2026-08-20T12:00:00.000Z",
|
|
34
33
|
"plan_digest": "1111111111111111111111111111111111111111111111111111111111111111",
|
|
@@ -20,11 +20,10 @@
|
|
|
20
20
|
"policy_label": "policy-contract",
|
|
21
21
|
"assurance_source": "policy",
|
|
22
22
|
"assurance_scope": {
|
|
23
|
-
"
|
|
24
|
-
"
|
|
23
|
+
"type": "selectors",
|
|
24
|
+
"selectors": [
|
|
25
25
|
"src/**"
|
|
26
|
-
]
|
|
27
|
-
"include_untracked": true
|
|
26
|
+
]
|
|
28
27
|
},
|
|
29
28
|
"activated_at": "2026-08-20T12:00:00.000Z",
|
|
30
29
|
"plan_digest": "1111111111111111111111111111111111111111111111111111111111111111",
|
|
@@ -22,11 +22,10 @@
|
|
|
22
22
|
"policy_label": "policy-contract",
|
|
23
23
|
"assurance_source": "policy",
|
|
24
24
|
"assurance_scope": {
|
|
25
|
-
"
|
|
26
|
-
"
|
|
25
|
+
"type": "selectors",
|
|
26
|
+
"selectors": [
|
|
27
27
|
"src/**"
|
|
28
|
-
]
|
|
29
|
-
"include_untracked": true
|
|
28
|
+
]
|
|
30
29
|
},
|
|
31
30
|
"activated_at": "2026-08-20T12:00:00.000Z",
|
|
32
31
|
"plan_digest": "1111111111111111111111111111111111111111111111111111111111111111",
|
|
@@ -28,10 +28,10 @@
|
|
|
28
28
|
},
|
|
29
29
|
"correlation": {
|
|
30
30
|
"type": "object",
|
|
31
|
-
"description": "
|
|
31
|
+
"description": "Non-authoritative controller metadata under the pinned 1.0 contract. String and aggregate byte limits are additionally enforced by the runtime and cannot be represented exactly in JSON Schema.",
|
|
32
32
|
"properties": {
|
|
33
33
|
"schema_version": {
|
|
34
|
-
"
|
|
34
|
+
"const": "1.0"
|
|
35
35
|
},
|
|
36
36
|
"run_id": {
|
|
37
37
|
"$ref": "#/$defs/ledgerCorrelationIdentifier"
|
|
@@ -63,19 +63,42 @@
|
|
|
63
63
|
"assurance_scope": {
|
|
64
64
|
"oneOf": [
|
|
65
65
|
{
|
|
66
|
-
"type": "object"
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
66
|
+
"type": "object",
|
|
67
|
+
"properties": {
|
|
68
|
+
"type": {
|
|
69
|
+
"const": "entire-run"
|
|
70
|
+
},
|
|
71
|
+
"selectors": {
|
|
72
|
+
"type": "array",
|
|
73
|
+
"maxItems": 0
|
|
74
|
+
}
|
|
75
|
+
},
|
|
76
|
+
"required": [
|
|
77
|
+
"type",
|
|
78
|
+
"selectors"
|
|
79
|
+
],
|
|
80
|
+
"additionalProperties": false
|
|
76
81
|
},
|
|
77
82
|
{
|
|
78
|
-
"type": "
|
|
83
|
+
"type": "object",
|
|
84
|
+
"properties": {
|
|
85
|
+
"type": {
|
|
86
|
+
"const": "selectors"
|
|
87
|
+
},
|
|
88
|
+
"selectors": {
|
|
89
|
+
"type": "array",
|
|
90
|
+
"minItems": 1,
|
|
91
|
+
"items": {
|
|
92
|
+
"type": "string",
|
|
93
|
+
"minLength": 1
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
},
|
|
97
|
+
"required": [
|
|
98
|
+
"type",
|
|
99
|
+
"selectors"
|
|
100
|
+
],
|
|
101
|
+
"additionalProperties": false
|
|
79
102
|
}
|
|
80
103
|
]
|
|
81
104
|
},
|
|
@@ -150,6 +173,8 @@
|
|
|
150
173
|
"DEPTH_EXCEEDED",
|
|
151
174
|
"FANOUT_EXCEEDED",
|
|
152
175
|
"EXECUTOR_UNAVAILABLE",
|
|
176
|
+
"MODEL_UNRESOLVED",
|
|
177
|
+
"GRANT_STORE_INVALID",
|
|
153
178
|
"CHILD_TIMED_OUT",
|
|
154
179
|
"CHILD_CANCELLED",
|
|
155
180
|
"CHILD_EXIT_NONZERO",
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"check-runner.d.ts","sourceRoot":"","sources":["../src/check-runner.ts"],"names":[],"mappings":"AAIA,OAAO,EAAwB,KAAK,mBAAmB,EAAE,MAAM,kBAAkB,CAAC;
|
|
1
|
+
{"version":3,"file":"check-runner.d.ts","sourceRoot":"","sources":["../src/check-runner.ts"],"names":[],"mappings":"AAIA,OAAO,EAAwB,KAAK,mBAAmB,EAAE,MAAM,kBAAkB,CAAC;AAUlF,OAAO,EAGL,KAAK,kBAAkB,EACvB,KAAK,eAAe,EAGrB,MAAM,gBAAgB,CAAC;AAIxB,MAAM,WAAW,eAAe;IAC9B,gGAAgG;IAChG,UAAU,EAAE,MAAM,CAAC;IACnB,yDAAyD;IACzD,IAAI,EAAE,MAAM,EAAE,CAAC;IACf,8CAA8C;IAC9C,GAAG,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC7B,mEAAmE;IACnE,WAAW,CAAC,EAAE,MAAM,EAAE,CAAC;IACvB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,+EAA+E;IAC/E,gBAAgB,CAAC,EAAE,eAAe,CAAC;CACpC;AAED,MAAM,WAAW,aAAa;IAC5B,OAAO,EAAE,CAAC,CAAC;IACX,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,eAAe,CAAC,CAAC;CACzC;AAED,MAAM,WAAW,YAAY;IAC3B,cAAc,EAAE,KAAK,CAAC;IACtB,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;IACnB,iBAAiB,EAAE,MAAM,CAAC;IAC1B,IAAI,EAAE,MAAM,EAAE,CAAC;IACf,WAAW,EAAE,MAAM,CAAC;IACpB,GAAG,EAAE,MAAM,CAAC;IACZ,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,MAAM,EAAE,MAAM,CAAC,OAAO,GAAG,IAAI,CAAC;IAC9B,SAAS,EAAE,OAAO,CAAC;IACnB,OAAO,EAAE,OAAO,CAAC;IACjB,SAAS,EAAE,OAAO,CAAC;IACnB,aAAa,EAAE,MAAM,CAAC;IACtB,YAAY,EAAE,MAAM,CAAC;IACrB,gBAAgB,EAAE,eAAe,CAAC;IAClC,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,CAAC,EAAE,mBAAmB,CAAC;CACnC;AAKD,wBAAgB,qBAAqB,CACnC,UAAU,EAAE,eAAe,EAC3B,SAAS,GAAE,MAAM,CAAC,UAAwB,GACzC,MAAM,CAAC,UAAU,CAWnB;AAkCD,wBAAsB,aAAa,CAAC,KAAK,EAAE;IACzC,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,aAAa,CAAC;IACxB,SAAS,EAAE,kBAAkB,CAAC;IAC9B,WAAW,CAAC,EAAE,mBAAmB,CAAC;IAClC,YAAY,CAAC,EAAE,MAAM,CAAC,UAAU,CAAC;IACjC,MAAM,CAAC,EAAE,WAAW,CAAC;IACrB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB,GAAG,OAAO,CAAC;IACV,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,MAAM,EAAE,MAAM,CAAC,OAAO,GAAG,IAAI,CAAC;IAC9B,OAAO,EAAE,YAAY,CAAC;CACvB,CAAC,CAyOD"}
|
package/dist/check-runner.js
CHANGED
|
@@ -3,6 +3,7 @@ import { chmod, mkdtemp, readFile, realpath, rm, stat, writeFile } from "node:fs
|
|
|
3
3
|
import { tmpdir } from "node:os";
|
|
4
4
|
import { basename, isAbsolute, join } from "node:path";
|
|
5
5
|
import { normaliseCorrelation } from "./correlation.js";
|
|
6
|
+
import { runWithFinalizers } from "./finalization.js";
|
|
6
7
|
import { appendLedgerEvent, buildCheckReceiptLedgerEvent, buildWorkspaceLeaseEvent, } from "./ledger.js";
|
|
7
8
|
import { computeGitCandidateIdentity } from "./git-identity.js";
|
|
8
9
|
import { runChild } from "./run-child.js";
|
|
@@ -77,173 +78,184 @@ export async function runNamedCheck(input) {
|
|
|
77
78
|
/** Failures of best-effort RECORDS, reported alongside whatever actually happened — never instead of it. */
|
|
78
79
|
const notes = [];
|
|
79
80
|
let releaseReason = "failed";
|
|
80
|
-
|
|
81
|
+
return runWithFinalizers(async () => {
|
|
81
82
|
try {
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
83
|
+
try {
|
|
84
|
+
lease = await acquireWorkspaceLease({
|
|
85
|
+
workspace: input.workspace, access: leaseAccess, leaseDir: input.leaseDir ?? defaultWorkspaceLeaseDir(),
|
|
86
|
+
ownerId, signal: input.signal,
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
catch (error) {
|
|
90
|
+
if (input.ledgerPath)
|
|
91
|
+
await appendLedgerEvent({ path: input.ledgerPath, strict: true }, buildWorkspaceLeaseEvent({
|
|
92
|
+
executionId, parentExecutionId,
|
|
93
|
+
childId: ownerId, workspaceId: input.workspace.workspaceId, root: input.workspace.root, access: leaseAccess,
|
|
94
|
+
outcome: "refused",
|
|
95
|
+
correlation, now: new Date(),
|
|
96
|
+
...(error instanceof GovernanceRefusal
|
|
97
|
+
? { refusal: { code: error.code, message: error.message, ...(error.details ? { details: error.details } : {}) } }
|
|
98
|
+
: {}),
|
|
99
|
+
}));
|
|
100
|
+
throw error;
|
|
101
|
+
}
|
|
102
|
+
if (input.ledgerPath) {
|
|
103
|
+
try {
|
|
104
|
+
await appendLedgerEvent({ path: input.ledgerPath, strict: true }, buildWorkspaceLeaseEvent({
|
|
105
|
+
executionId, parentExecutionId,
|
|
106
|
+
childId: ownerId, workspaceId: input.workspace.workspaceId, root: input.workspace.root, access: leaseAccess,
|
|
107
|
+
outcome: leaseAcquisitionOutcome(leaseAccess, lease.recovered), recovered: lease.recovered,
|
|
108
|
+
correlation, now: new Date(),
|
|
109
|
+
}));
|
|
110
|
+
}
|
|
111
|
+
catch (error) {
|
|
112
|
+
await lease.release("ledger-failed");
|
|
113
|
+
lease = undefined;
|
|
114
|
+
throw error;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
const before = await computeGitCandidateIdentity(input.workspace);
|
|
118
|
+
for (const [name, supplied, actual] of [
|
|
119
|
+
["head_sha", correlation?.head_sha, before.headSha], ["tree_sha", correlation?.tree_sha, before.treeSha],
|
|
120
|
+
]) {
|
|
121
|
+
if (supplied && supplied !== actual)
|
|
122
|
+
throw new GovernanceRefusal(refusal("CHECK_IDENTITY_MISMATCH", `check ${JSON.stringify(input.checkId)} ${name} does not match the validated workspace`, { check_id: input.checkId, supplied, actual }));
|
|
123
|
+
}
|
|
124
|
+
const executableBytes = await readFile(executable);
|
|
125
|
+
const executableSha256 = createHash("sha256").update(executableBytes).digest("hex");
|
|
126
|
+
stagedDir = await mkdtemp(join(tmpdir(), "pi-daddy-check-exec-"));
|
|
127
|
+
const stagedExecutable = join(stagedDir, basename(executable));
|
|
128
|
+
await writeFile(stagedExecutable, executableBytes, { mode: 0o700 });
|
|
129
|
+
await chmod(stagedExecutable, 0o700);
|
|
130
|
+
const leaseAbort = new AbortController();
|
|
131
|
+
let leaseLost = false;
|
|
132
|
+
lease.lost.then(() => { leaseLost = true; leaseAbort.abort(); });
|
|
133
|
+
const checkSignal = input.signal ? AbortSignal.any([input.signal, leaseAbort.signal]) : leaseAbort.signal;
|
|
134
|
+
const started = new Date();
|
|
135
|
+
const result = await runChild({
|
|
136
|
+
command: "setpriv", args: ["--pdeathsig", "KILL", "--", stagedExecutable, ...definition.argv],
|
|
137
|
+
env: buildCheckEnvironment(definition, input.inheritedEnv), cwd: input.workspace.root,
|
|
138
|
+
signal: checkSignal, timeoutMs: definition.timeout_ms, maxOutputBytes: definition.max_output_bytes,
|
|
139
|
+
onSpawn: (pid) => lease.attachProcess(pid),
|
|
85
140
|
});
|
|
86
|
-
|
|
87
|
-
|
|
141
|
+
const ended = new Date();
|
|
142
|
+
releaseReason = input.signal?.aborted ? "cancelled" : result.timedOut ? "timeout" : result.aborted ? "failed" : "completed";
|
|
143
|
+
// Every throw below re-stamps this. Without that, a check refused for CHECK_IDENTITY_MISMATCH still
|
|
144
|
+
// wrote `released / completed` from the `finally` — the LAST lease event for that owner — so the trail
|
|
145
|
+
// still read like a clean run and `verifyLedger` still counted a clean release. The R-114 fix added a
|
|
146
|
+
// refusal line beside the misleading one instead of correcting it.
|
|
147
|
+
if (result.spawnError)
|
|
148
|
+
throw new GovernanceRefusal(refusal("EXECUTOR_UNAVAILABLE", `check ${JSON.stringify(input.checkId)} could not start its constrained executor`, { check_id: input.checkId }));
|
|
149
|
+
if (leaseLost)
|
|
150
|
+
throw new GovernanceRefusal(refusal("WORKSPACE_LEASE_STALE", `check ${JSON.stringify(input.checkId)} lost its workspace lease while running`, { check_id: input.checkId }));
|
|
151
|
+
const after = await computeGitCandidateIdentity(input.workspace);
|
|
152
|
+
if (leaseLost)
|
|
153
|
+
throw new GovernanceRefusal(refusal("WORKSPACE_LEASE_STALE", `check ${JSON.stringify(input.checkId)} lost its workspace lease before receipt`, { check_id: input.checkId }));
|
|
154
|
+
if (before.headSha !== after.headSha || before.treeSha !== after.treeSha) {
|
|
155
|
+
throw new GovernanceRefusal(refusal("CHECK_IDENTITY_MISMATCH", `check ${JSON.stringify(input.checkId)} workspace changed while it ran`, { check_id: input.checkId }));
|
|
156
|
+
}
|
|
157
|
+
// The candidate is now measured and the process is dead. End the lease before writing the receipt so
|
|
158
|
+
// lease loss cannot race a blocked ledger append and leave behind evidence from an unprotected run.
|
|
159
|
+
// Record what release actually DID. This asserted `released` unconditionally and threw the outcome
|
|
160
|
+
// away, so the evidence path reproduced the very defect the outcome union was added to expose: a lost
|
|
161
|
+
// or unrecorded handover ledgered as a clean one, with `lease = undefined` stopping the `finally` from
|
|
162
|
+
// ever noticing (R-100/R-103, found while reviewing the fix for R-99).
|
|
163
|
+
const releaseOutcome = await lease.release(releaseReason);
|
|
164
|
+
lease = undefined;
|
|
165
|
+
if (leaseLost)
|
|
166
|
+
throw new GovernanceRefusal(refusal("WORKSPACE_LEASE_STALE", `check ${JSON.stringify(input.checkId)} lost its workspace lease before receipt`, { check_id: input.checkId }));
|
|
88
167
|
if (input.ledgerPath)
|
|
89
168
|
await appendLedgerEvent({ path: input.ledgerPath, strict: true }, buildWorkspaceLeaseEvent({
|
|
90
169
|
executionId, parentExecutionId,
|
|
91
170
|
childId: ownerId, workspaceId: input.workspace.workspaceId, root: input.workspace.root, access: leaseAccess,
|
|
92
|
-
outcome:
|
|
93
|
-
correlation, now: new Date(),
|
|
94
|
-
...(error instanceof GovernanceRefusal
|
|
95
|
-
? { refusal: { code: error.code, message: error.message, ...(error.details ? { details: error.details } : {}) } }
|
|
96
|
-
: {}),
|
|
171
|
+
outcome: leaseReleaseLedgerOutcome(releaseOutcome, releaseReason), releaseReason, correlation, now: new Date(),
|
|
97
172
|
}));
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
173
|
+
const body = {
|
|
174
|
+
schema_version: "1.0", check_id: input.checkId, executable, executable_sha256: executableSha256,
|
|
175
|
+
argv: [...definition.argv], argv_sha256: argvSha256, cwd: input.workspace.root, cwd_sha256: cwdSha256,
|
|
176
|
+
started_at: started.toISOString(), ended_at: ended.toISOString(), exit_code: result.code,
|
|
177
|
+
signal: result.signal ?? null, timed_out: result.timedOut, aborted: result.aborted, truncated: result.truncated,
|
|
178
|
+
output_sha256: createHash("sha256").update(result.text, "utf8").digest("hex"),
|
|
179
|
+
workspace_id: input.workspace.workspaceId, workspace_access: access,
|
|
180
|
+
head_sha: before.headSha, tree_sha: before.treeSha, ...(correlation ? { correlation } : {}),
|
|
181
|
+
};
|
|
182
|
+
const receipt = { receipt_id: receiptId(body), ...body };
|
|
183
|
+
if (input.ledgerPath) {
|
|
184
|
+
await appendLedgerEvent({ path: input.ledgerPath, strict: true }, buildCheckReceiptLedgerEvent({
|
|
103
185
|
executionId, parentExecutionId,
|
|
104
|
-
childId: ownerId,
|
|
105
|
-
|
|
106
|
-
correlation, now: new Date(),
|
|
186
|
+
childId: ownerId, receiptId: receipt.receipt_id, workspaceId: input.workspace.workspaceId,
|
|
187
|
+
checkId: input.checkId, treeSha: receipt.tree_sha, correlation, now: ended,
|
|
107
188
|
}));
|
|
108
189
|
}
|
|
109
|
-
|
|
110
|
-
await lease.release("ledger-failed");
|
|
111
|
-
lease = undefined;
|
|
112
|
-
throw error;
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
|
-
const before = await computeGitCandidateIdentity(input.workspace);
|
|
116
|
-
for (const [name, supplied, actual] of [
|
|
117
|
-
["head_sha", correlation?.head_sha, before.headSha], ["tree_sha", correlation?.tree_sha, before.treeSha],
|
|
118
|
-
]) {
|
|
119
|
-
if (supplied && supplied !== actual)
|
|
120
|
-
throw new GovernanceRefusal(refusal("CHECK_IDENTITY_MISMATCH", `check ${JSON.stringify(input.checkId)} ${name} does not match the validated workspace`, { check_id: input.checkId, supplied, actual }));
|
|
121
|
-
}
|
|
122
|
-
const executableBytes = await readFile(executable);
|
|
123
|
-
const executableSha256 = createHash("sha256").update(executableBytes).digest("hex");
|
|
124
|
-
stagedDir = await mkdtemp(join(tmpdir(), "pi-daddy-check-exec-"));
|
|
125
|
-
const stagedExecutable = join(stagedDir, basename(executable));
|
|
126
|
-
await writeFile(stagedExecutable, executableBytes, { mode: 0o700 });
|
|
127
|
-
await chmod(stagedExecutable, 0o700);
|
|
128
|
-
const leaseAbort = new AbortController();
|
|
129
|
-
let leaseLost = false;
|
|
130
|
-
lease.lost.then(() => { leaseLost = true; leaseAbort.abort(); });
|
|
131
|
-
const checkSignal = input.signal ? AbortSignal.any([input.signal, leaseAbort.signal]) : leaseAbort.signal;
|
|
132
|
-
const started = new Date();
|
|
133
|
-
const result = await runChild({
|
|
134
|
-
command: "setpriv", args: ["--pdeathsig", "KILL", "--", stagedExecutable, ...definition.argv],
|
|
135
|
-
env: buildCheckEnvironment(definition, input.inheritedEnv), cwd: input.workspace.root,
|
|
136
|
-
signal: checkSignal, timeoutMs: definition.timeout_ms, maxOutputBytes: definition.max_output_bytes,
|
|
137
|
-
onSpawn: (pid) => lease.attachProcess(pid),
|
|
138
|
-
});
|
|
139
|
-
const ended = new Date();
|
|
140
|
-
releaseReason = input.signal?.aborted ? "cancelled" : result.timedOut ? "timeout" : result.aborted ? "failed" : "completed";
|
|
141
|
-
// Every throw below re-stamps this. Without that, a check refused for CHECK_IDENTITY_MISMATCH still
|
|
142
|
-
// wrote `released / completed` from the `finally` — the LAST lease event for that owner — so the trail
|
|
143
|
-
// still read like a clean run and `verifyLedger` still counted a clean release. The R-114 fix added a
|
|
144
|
-
// refusal line beside the misleading one instead of correcting it.
|
|
145
|
-
if (result.spawnError)
|
|
146
|
-
throw new GovernanceRefusal(refusal("EXECUTOR_UNAVAILABLE", `check ${JSON.stringify(input.checkId)} could not start its constrained executor`, { check_id: input.checkId }));
|
|
147
|
-
if (leaseLost)
|
|
148
|
-
throw new GovernanceRefusal(refusal("WORKSPACE_LEASE_STALE", `check ${JSON.stringify(input.checkId)} lost its workspace lease while running`, { check_id: input.checkId }));
|
|
149
|
-
const after = await computeGitCandidateIdentity(input.workspace);
|
|
150
|
-
if (leaseLost)
|
|
151
|
-
throw new GovernanceRefusal(refusal("WORKSPACE_LEASE_STALE", `check ${JSON.stringify(input.checkId)} lost its workspace lease before receipt`, { check_id: input.checkId }));
|
|
152
|
-
if (before.headSha !== after.headSha || before.treeSha !== after.treeSha) {
|
|
153
|
-
throw new GovernanceRefusal(refusal("CHECK_IDENTITY_MISMATCH", `check ${JSON.stringify(input.checkId)} workspace changed while it ran`, { check_id: input.checkId }));
|
|
154
|
-
}
|
|
155
|
-
// The candidate is now measured and the process is dead. End the lease before writing the receipt so
|
|
156
|
-
// lease loss cannot race a blocked ledger append and leave behind evidence from an unprotected run.
|
|
157
|
-
// Record what release actually DID. This asserted `released` unconditionally and threw the outcome
|
|
158
|
-
// away, so the evidence path reproduced the very defect the outcome union was added to expose: a lost
|
|
159
|
-
// or unrecorded handover ledgered as a clean one, with `lease = undefined` stopping the `finally` from
|
|
160
|
-
// ever noticing (R-100/R-103, found while reviewing the fix for R-99).
|
|
161
|
-
const releaseOutcome = await lease.release(releaseReason);
|
|
162
|
-
lease = undefined;
|
|
163
|
-
if (leaseLost)
|
|
164
|
-
throw new GovernanceRefusal(refusal("WORKSPACE_LEASE_STALE", `check ${JSON.stringify(input.checkId)} lost its workspace lease before receipt`, { check_id: input.checkId }));
|
|
165
|
-
if (input.ledgerPath)
|
|
166
|
-
await appendLedgerEvent({ path: input.ledgerPath, strict: true }, buildWorkspaceLeaseEvent({
|
|
167
|
-
executionId, parentExecutionId,
|
|
168
|
-
childId: ownerId, workspaceId: input.workspace.workspaceId, root: input.workspace.root, access: leaseAccess,
|
|
169
|
-
outcome: leaseReleaseLedgerOutcome(releaseOutcome, releaseReason), releaseReason, correlation, now: new Date(),
|
|
170
|
-
}));
|
|
171
|
-
const body = {
|
|
172
|
-
schema_version: "1.0", check_id: input.checkId, executable, executable_sha256: executableSha256,
|
|
173
|
-
argv: [...definition.argv], argv_sha256: argvSha256, cwd: input.workspace.root, cwd_sha256: cwdSha256,
|
|
174
|
-
started_at: started.toISOString(), ended_at: ended.toISOString(), exit_code: result.code,
|
|
175
|
-
signal: result.signal ?? null, timed_out: result.timedOut, aborted: result.aborted, truncated: result.truncated,
|
|
176
|
-
output_sha256: createHash("sha256").update(result.text, "utf8").digest("hex"),
|
|
177
|
-
workspace_id: input.workspace.workspaceId, workspace_access: access,
|
|
178
|
-
head_sha: before.headSha, tree_sha: before.treeSha, ...(correlation ? { correlation } : {}),
|
|
179
|
-
};
|
|
180
|
-
const receipt = { receipt_id: receiptId(body), ...body };
|
|
181
|
-
if (input.ledgerPath) {
|
|
182
|
-
await appendLedgerEvent({ path: input.ledgerPath, strict: true }, buildCheckReceiptLedgerEvent({
|
|
183
|
-
executionId, parentExecutionId,
|
|
184
|
-
childId: ownerId, receiptId: receipt.receipt_id, workspaceId: input.workspace.workspaceId,
|
|
185
|
-
checkId: input.checkId, treeSha: receipt.tree_sha, correlation, now: ended,
|
|
186
|
-
}));
|
|
187
|
-
}
|
|
188
|
-
return { output: result.text, exitCode: result.code, signal: result.signal ?? null, receipt };
|
|
189
|
-
}
|
|
190
|
-
catch (error) {
|
|
191
|
-
// The run did not complete, so the release must not be recorded as if it had. Without this the
|
|
192
|
-
// `finally` wrote `released / completed` as the LAST lease event for a refused check, so the trail
|
|
193
|
-
// still read like a clean run and `verifyLedger` still counted a clean release — the R-114 fix put a
|
|
194
|
-
// refusal line beside the misleading one instead of correcting it.
|
|
195
|
-
if (releaseReason === "completed")
|
|
196
|
-
releaseReason = "refused";
|
|
197
|
-
// The single most important negative result this feature can produce — CHECK_IDENTITY_MISMATCH, "the
|
|
198
|
-
// workspace changed while the check ran" — used to leave NO ledger evidence at all, while the
|
|
199
|
-
// `finally` recorded `released, reason: completed`, so the trail read like a clean run (R-114).
|
|
200
|
-
if (input.ledgerPath) {
|
|
201
|
-
await appendLedgerEvent({
|
|
202
|
-
path: input.ledgerPath,
|
|
203
|
-
// Best effort, and loud: this records a failure, so it must not replace the failure. A strict
|
|
204
|
-
// append that threw here would substitute "ledger write failed" for the evidence-integrity
|
|
205
|
-
// refusal that actually happened (R-115).
|
|
206
|
-
strict: false,
|
|
207
|
-
// NOT empty. `onFailure` exists because a non-strict append is a choice not to fail closed and
|
|
208
|
-
// never a choice to be silent — the comment above says "loud", and an empty callback is the
|
|
209
|
-
// definition of swallowing. This guards the evidence record itself, so losing it silently is
|
|
210
|
-
// how a mutated workspace came to leave a trail that read like a clean run.
|
|
211
|
-
onFailure: (cause) => { notes.push(`check refusal record failed: ${String(cause)}`); },
|
|
212
|
-
}, buildWorkspaceLeaseEvent({
|
|
213
|
-
executionId, parentExecutionId,
|
|
214
|
-
childId: ownerId, workspaceId: input.workspace.workspaceId, root: input.workspace.root, access: leaseAccess,
|
|
215
|
-
outcome: "refused",
|
|
216
|
-
refusal: error instanceof GovernanceRefusal
|
|
217
|
-
? { code: error.code, message: error.message, ...(error.details ? { details: error.details } : {}) }
|
|
218
|
-
: refusal("CHECK_IDENTITY_UNAVAILABLE", `check ${JSON.stringify(input.checkId)} failed (${String(error)})`),
|
|
219
|
-
correlation, now: new Date(),
|
|
220
|
-
}));
|
|
190
|
+
return { output: result.text, exitCode: result.code, signal: result.signal ?? null, receipt };
|
|
221
191
|
}
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
//
|
|
230
|
-
|
|
231
|
-
|
|
192
|
+
catch (error) {
|
|
193
|
+
// The run did not complete, so the release must not be recorded as if it had. Without this the
|
|
194
|
+
// `finally` wrote `released / completed` as the LAST lease event for a refused check, so the trail
|
|
195
|
+
// still read like a clean run and `verifyLedger` still counted a clean release — the R-114 fix put a
|
|
196
|
+
// refusal line beside the misleading one instead of correcting it.
|
|
197
|
+
if (releaseReason === "completed")
|
|
198
|
+
releaseReason = "refused";
|
|
199
|
+
// The single most important negative result this feature can produce — CHECK_IDENTITY_MISMATCH, "the
|
|
200
|
+
// workspace changed while the check ran" — used to leave NO ledger evidence at all, while the
|
|
201
|
+
// `finally` recorded `released, reason: completed`, so the trail read like a clean run (R-114).
|
|
202
|
+
if (input.ledgerPath) {
|
|
232
203
|
await appendLedgerEvent({
|
|
233
204
|
path: input.ledgerPath,
|
|
205
|
+
// Best effort, and loud: this records a failure, so it must not replace the failure. A strict
|
|
206
|
+
// append that threw here would substitute "ledger write failed" for the evidence-integrity
|
|
207
|
+
// refusal that actually happened (R-115).
|
|
234
208
|
strict: false,
|
|
235
|
-
onFailure
|
|
209
|
+
// NOT empty. `onFailure` exists because a non-strict append is a choice not to fail closed and
|
|
210
|
+
// never a choice to be silent — the comment above says "loud", and an empty callback is the
|
|
211
|
+
// definition of swallowing. This guards the evidence record itself, so losing it silently is
|
|
212
|
+
// how a mutated workspace came to leave a trail that read like a clean run.
|
|
213
|
+
onFailure: (cause) => { notes.push(`check refusal record failed: ${String(cause)}`); },
|
|
236
214
|
}, buildWorkspaceLeaseEvent({
|
|
237
215
|
executionId, parentExecutionId,
|
|
238
216
|
childId: ownerId, workspaceId: input.workspace.workspaceId, root: input.workspace.root, access: leaseAccess,
|
|
239
|
-
outcome:
|
|
240
|
-
|
|
241
|
-
:
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
releaseReason, correlation, now: new Date(),
|
|
217
|
+
outcome: "refused",
|
|
218
|
+
refusal: error instanceof GovernanceRefusal
|
|
219
|
+
? { code: error.code, message: error.message, ...(error.details ? { details: error.details } : {}) }
|
|
220
|
+
: refusal("CHECK_IDENTITY_UNAVAILABLE", `check ${JSON.stringify(input.checkId)} failed (${String(error)})`),
|
|
221
|
+
correlation, now: new Date(),
|
|
245
222
|
}));
|
|
223
|
+
}
|
|
224
|
+
throw error;
|
|
246
225
|
}
|
|
247
|
-
}
|
|
226
|
+
}, [
|
|
227
|
+
{
|
|
228
|
+
label: "staged check cleanup failed",
|
|
229
|
+
run: async () => {
|
|
230
|
+
if (stagedDir)
|
|
231
|
+
await rm(stagedDir, { recursive: true, force: true });
|
|
232
|
+
},
|
|
233
|
+
},
|
|
234
|
+
{
|
|
235
|
+
label: "workspace lease finalizer failed",
|
|
236
|
+
run: async () => {
|
|
237
|
+
if (!lease)
|
|
238
|
+
return;
|
|
239
|
+
// `release()` cannot throw (R-99), and the record of it is best-effort for the same reason as
|
|
240
|
+
// above: a finalizer must not destroy whatever the caller was already returning or raising.
|
|
241
|
+
const outcome = await lease.release(releaseReason);
|
|
242
|
+
if (input.ledgerPath)
|
|
243
|
+
await appendLedgerEvent({
|
|
244
|
+
path: input.ledgerPath,
|
|
245
|
+
strict: false,
|
|
246
|
+
onFailure: (cause) => { notes.push(`check lease release record failed: ${String(cause)}`); },
|
|
247
|
+
}, buildWorkspaceLeaseEvent({
|
|
248
|
+
executionId, parentExecutionId,
|
|
249
|
+
childId: ownerId, workspaceId: input.workspace.workspaceId, root: input.workspace.root, access: leaseAccess,
|
|
250
|
+
outcome: outcome === "lost"
|
|
251
|
+
? "lost"
|
|
252
|
+
: outcome === "released-unrecorded"
|
|
253
|
+
? "released-unrecorded"
|
|
254
|
+
: releaseReason === "timeout" ? "timeout" : "released",
|
|
255
|
+
releaseReason, correlation, now: new Date(),
|
|
256
|
+
}));
|
|
257
|
+
},
|
|
258
|
+
},
|
|
259
|
+
]);
|
|
248
260
|
}
|
|
249
261
|
//# sourceMappingURL=check-runner.js.map
|