@reticlehq/server 3.2.0 → 3.3.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/SKILL.md +16 -13
- package/dist/command/cli/adhoc-suite.d.ts +19 -0
- package/dist/command/cli/adhoc-suite.js +88 -0
- package/dist/command/cli/adhoc-verdict.d.ts +23 -2
- package/dist/command/cli/adhoc-verdict.js +18 -10
- package/dist/command/cli/cli-doctor.js +25 -5
- package/dist/command/cli/cli-parse-grammar.d.ts +7 -0
- package/dist/command/cli/cli-parse-grammar.js +7 -0
- package/dist/command/cli/cli-parse-verify.d.ts +2 -5
- package/dist/command/cli/cli-parse-verify.js +86 -13
- package/dist/command/cli/cli-parse.d.ts +7 -1
- package/dist/command/cli/cli-parse.js +33 -5
- package/dist/command/cli/cli-verify.d.ts +59 -0
- package/dist/command/cli/cli-verify.js +129 -17
- package/dist/command/cli/cloud-kit.d.ts +0 -2
- package/dist/command/cli/cloud-kit.js +1 -1
- package/dist/command/cli/doctor/doctor-sessions-line.d.ts +2 -1
- package/dist/command/cli/launch/cli-launch.d.ts +1 -29
- package/dist/command/cli/launch/cli-launch.js +6 -40
- package/dist/command/cli/ports/resolve/cli-port.d.ts +13 -0
- package/dist/command/cli/ports/resolve/cli-port.js +45 -1
- package/dist/command/cli/report-command.d.ts +16 -0
- package/dist/command/cli/report-command.js +97 -0
- package/dist/command/cli.js +5 -0
- package/dist/command/daemon/binding/daemon-status-probe.d.ts +30 -0
- package/dist/command/daemon/binding/daemon-status-probe.js +45 -0
- package/dist/command/daemon/binding/port-presence.d.ts +1 -1
- package/dist/command/daemon/idle-shutdown.d.ts +2 -1
- package/dist/command/setup/init/init-host.js +8 -0
- package/dist/features/crawl/crawl.d.ts +2 -0
- package/dist/features/crawl/crawl.js +1 -0
- package/dist/features/harness/jev-driver.js +6 -9
- package/dist/features/harness/platform-config.d.ts +10 -0
- package/dist/features/harness/platform-config.js +8 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +40 -95
- package/dist/judgement/capsule/capsule-store.d.ts +31 -2
- package/dist/judgement/capsule/capsule-store.js +58 -6
- package/dist/judgement/capsule/capsule.js +5 -0
- package/dist/judgement/capsule/causal-summary.d.ts +13 -1
- package/dist/judgement/capsule/causal-summary.js +7 -0
- package/dist/judgement/domain/domain-model.js +38 -9
- package/dist/judgement/outcome/assertion-integrity.d.ts +3 -3
- package/dist/judgement/phenomena/phenomena.d.ts +2 -1
- package/dist/judgement/runs/artifact/gap-report.d.ts +17 -0
- package/dist/judgement/runs/artifact/gap-report.js +46 -0
- package/dist/judgement/runs/artifact/gap-summary.d.ts +58 -0
- package/dist/judgement/runs/artifact/gap-summary.js +85 -0
- package/dist/judgement/runs/artifact/render-report.js +4 -1
- package/dist/judgement/runs/artifact/to-artifact.d.ts +7 -2
- package/dist/judgement/runs/artifact/to-artifact.js +1 -1
- package/dist/judgement/runs/context-tools.js +21 -3
- package/dist/judgement/runs/drive-run.js +7 -2
- package/dist/language/flows/annotate-notes/annotate.d.ts +0 -15
- package/dist/language/flows/annotate-notes/annotate.js +66 -18
- package/dist/language/flows/flow-anchor.d.ts +12 -0
- package/dist/language/flows/flow-anchor.js +20 -0
- package/dist/language/flows/flow-expect-grammar.d.ts +2 -2
- package/dist/language/flows/flow-expect-grammar.js +92 -11
- package/dist/language/flows/flow-intent.js +1 -1
- package/dist/language/flows/flow-learning.d.ts +3 -0
- package/dist/language/flows/flow-learning.js +39 -8
- package/dist/language/flows/flow-replay-run.d.ts +31 -9
- package/dist/language/flows/flow-replay-run.js +146 -33
- package/dist/language/flows/flow-replay-types.d.ts +9 -0
- package/dist/language/flows/flow-replay.d.ts +12 -11
- package/dist/language/flows/flow-replay.js +79 -109
- package/dist/language/flows/flow-step-runners.d.ts +26 -10
- package/dist/language/flows/flow-step-runners.js +20 -4
- package/dist/language/flows/flow-success.d.ts +21 -6
- package/dist/language/flows/flow-success.js +50 -138
- package/dist/language/flows/flows.d.ts +8 -4
- package/dist/language/flows/flows.js +11 -2
- package/dist/language/flows/heal.d.ts +9 -1
- package/dist/language/flows/heal.js +24 -2
- package/dist/language/flows/learned-sources.d.ts +21 -0
- package/dist/language/flows/learned-sources.js +84 -0
- package/dist/language/flows/recording/recording-backtrack.d.ts +0 -7
- package/dist/language/flows/recording/recording-backtrack.js +1 -1
- package/dist/language/flows/recording/replay-halt.d.ts +2 -1
- package/dist/language/flows/recording/tape/recordings.d.ts +2 -2
- package/dist/language/flows/replay.js +12 -8
- package/dist/language/flows/stores/annotation-store.d.ts +5 -5
- package/dist/language/flows/stores/assertion-tiers-store.js +2 -3
- package/dist/language/flows/stores/flake-store.js +2 -3
- package/dist/language/flows/suite/coverage.d.ts +1 -1
- package/dist/language/flows/suite/parallel-suite.d.ts +2 -1
- package/dist/language/flows/verify-change-tools.js +14 -0
- package/dist/memory/cloud/cloud-sync.d.ts +0 -2
- package/dist/memory/cloud/cloud-sync.js +1 -1
- package/dist/memory/cloud/harness-config.d.ts +2 -0
- package/dist/memory/cloud/sync-cycle.d.ts +12 -0
- package/dist/memory/cloud/sync-cycle.js +29 -4
- package/dist/memory/cloud/sync-daemon.d.ts +13 -0
- package/dist/memory/cloud/sync-daemon.js +21 -0
- package/dist/memory/cloud/sync-disk.js +11 -6
- package/dist/memory/impact/impact-store.js +6 -0
- package/dist/memory/intent/inline-intent.js +8 -3
- package/dist/memory/intent/intent-shard-store.d.ts +33 -47
- package/dist/memory/intent/intent-shard-store.js +252 -140
- package/dist/memory/intent/intent-shard.d.ts +10 -11
- package/dist/memory/intent/intent-shard.js +1 -1
- package/dist/memory/intent/intent-store.d.ts +13 -8
- package/dist/memory/intent/intent-store.js +46 -90
- package/dist/memory/intent/intent-tools.js +34 -23
- package/dist/memory/journal/ambient-store.js +2 -3
- package/dist/memory/journal/deviation-report.js +4 -1
- package/dist/memory/journal/deviation-service.js +10 -2
- package/dist/memory/journal/drive-flow.d.ts +2 -2
- package/dist/memory/journal/drive-flow.js +21 -12
- package/dist/memory/journal/envelope-store.js +2 -3
- package/dist/memory/journal/journal-recorder.d.ts +21 -0
- package/dist/memory/journal/on-disk/retain-policy.d.ts +34 -0
- package/dist/memory/journal/on-disk/retain-policy.js +44 -0
- package/dist/memory/journal/on-disk/retention.d.ts +14 -10
- package/dist/memory/journal/on-disk/retention.js +13 -12
- package/dist/memory/journal/on-disk/startup-maintenance.d.ts +42 -0
- package/dist/memory/journal/on-disk/startup-maintenance.js +14 -0
- package/dist/memory/journal/on-disk/workspace-gitignore.d.ts +0 -11
- package/dist/memory/journal/on-disk/workspace-gitignore.js +6 -45
- package/dist/memory/journal/on-disk/workspace-tiers.d.ts +52 -0
- package/dist/memory/journal/on-disk/workspace-tiers.js +88 -0
- package/dist/memory/journal/route-template.d.ts +22 -0
- package/dist/memory/journal/route-template.js +44 -0
- package/dist/memory/journal/session-end.d.ts +4 -1
- package/dist/memory/journal/session-end.js +64 -6
- package/dist/memory/journal/session-journal.d.ts +8 -1
- package/dist/memory/journal/session-journal.js +10 -0
- package/dist/memory/project/baselines.d.ts +1 -1
- package/dist/memory/project/dir/reticle-dir.d.ts +1 -1
- package/dist/memory/project/fs/fs-port.js +44 -5
- package/dist/memory/project/fs/write-atomic.d.ts +26 -0
- package/dist/memory/project/fs/write-atomic.js +48 -0
- package/dist/memory/recall/prior/connection-memory.d.ts +6 -0
- package/dist/memory/recall/prior/connection-memory.js +8 -0
- package/dist/memory/recall/registered-projects.d.ts +2 -1
- package/dist/portal/bridge/auth-failure-reason.js +5 -2
- package/dist/portal/input/real-input.js +2 -1
- package/dist/portal/pool/browser-pool.d.ts +10 -0
- package/dist/portal/pool/browser-pool.js +10 -0
- package/dist/portal/pool/playwright-launcher.js +1 -0
- package/dist/portal/pool/pool-contract.d.ts +2 -0
- package/dist/portal/pool/storage-seed.d.ts +2 -1
- package/dist/portal/pool/zero-install.d.ts +27 -0
- package/dist/portal/pool/zero-install.js +54 -0
- package/dist/portal/realm/web-realm.js +6 -0
- package/dist/portal/session/dev-server/dev-server-probe.d.ts +2 -1
- package/dist/portal/session/dev-server/served-document.d.ts +21 -0
- package/dist/portal/session/dev-server/served-document.js +57 -0
- package/dist/portal/session/facts/background-traffic.d.ts +9 -0
- package/dist/portal/session/facts/background-traffic.js +25 -0
- package/dist/portal/session/live-control-tools.js +48 -3
- package/dist/portal/session/presence/attachment-history.d.ts +13 -2
- package/dist/portal/session/session-health.d.ts +61 -0
- package/dist/portal/session/session-health.js +40 -1
- package/dist/portal/session/session-info.d.ts +6 -0
- package/dist/portal/session/session-manager.d.ts +9 -0
- package/dist/portal/session/session-manager.js +23 -2
- package/dist/portal/session/session-successor.d.ts +11 -0
- package/dist/portal/session/session-successor.js +14 -1
- package/dist/portal/session/session-verdict-facts.d.ts +28 -0
- package/dist/portal/session/session-verdict-facts.js +15 -0
- package/dist/portal/session/session.d.ts +5 -0
- package/dist/portal/session/session.js +19 -7
- package/dist/surface/mcp/mcp-proxy.js +3 -3
- package/dist/surface/mcp/proxy-idle-exit.d.ts +2 -1
- package/dist/surface/mcp/server-instructions.js +12 -1
- package/dist/surface/tools/act/already-true.d.ts +40 -0
- package/dist/surface/tools/act/already-true.js +36 -0
- package/dist/surface/tools/act/navigation/navigate-arrival.d.ts +6 -0
- package/dist/surface/tools/act/navigation/navigate-arrival.js +13 -0
- package/dist/surface/tools/act/settle-in-flight.d.ts +6 -2
- package/dist/surface/tools/act/settle-in-flight.js +29 -6
- package/dist/surface/tools/act-capsule.js +22 -7
- package/dist/surface/tools/act-tools.js +25 -24
- package/dist/surface/tools/args/numeric-bounds.d.ts +15 -2
- package/dist/surface/tools/args/numeric-bounds.js +30 -3
- package/dist/surface/tools/assert/assert-grade.js +14 -2
- package/dist/surface/tools/assert/assert-verdict.js +30 -4
- package/dist/surface/tools/browser-tools.js +4 -3
- package/dist/surface/tools/dynamic-tools.js +6 -0
- package/dist/surface/tools/feedback-tools.d.ts +2 -1
- package/dist/surface/tools/feedback-tools.js +1 -1
- package/dist/surface/tools/invoke-tool.js +17 -4
- package/dist/surface/tools/lease-hint.d.ts +8 -0
- package/dist/surface/tools/lease-hint.js +10 -0
- package/dist/surface/tools/lease-tools.d.ts +16 -28
- package/dist/surface/tools/lease-tools.js +53 -5
- package/dist/surface/tools/lineage-tools.js +1 -1
- package/dist/surface/tools/observe-tools.js +14 -6
- package/dist/surface/tools/real-input-attempt.d.ts +0 -5
- package/dist/surface/tools/real-input-attempt.js +1 -1
- package/dist/surface/tools/tool-kit.d.ts +5 -0
- package/dist/surface/tools/tool-kit.js +6 -1
- package/dist/surface/tools/tool-surface.js +17 -5
- package/dist/surface/tools/tools.js +64 -29
- package/dist/telemetry/feedback-local.d.ts +1 -0
- package/dist/telemetry/feedback-local.js +1 -3
- package/dist/telemetry/feedback.d.ts +1 -1
- package/dist/telemetry/hud-metrics.d.ts +32 -0
- package/dist/telemetry/hud-metrics.js +95 -0
- package/dist/telemetry/session-metrics.d.ts +5 -0
- package/dist/telemetry/session-metrics.js +15 -1
- package/dist/wire-journal.d.ts +31 -0
- package/dist/wire-journal.js +100 -0
- package/docs/README.md +1 -0
- package/docs/cli/cloud.mdx +9 -7
- package/docs/cli/init.mdx +1 -0
- package/docs/cli/report.mdx +44 -0
- package/docs/desktop.mdx +1 -1
- package/docs/flows.md +11 -0
- package/docs/frameworks.mdx +113 -1
- package/docs/install-agentic.mdx +36 -0
- package/docs/install-manual.mdx +1 -1
- package/docs/packages/tauri.mdx +1 -1
- package/docs/packages.mdx +1 -1
- package/docs/predicates.mdx +128 -4
- package/docs/tools/query.mdx +10 -10
- package/docs/usage.md +1 -1
- package/docs/what-is-recorded.md +62 -0
- package/package.json +6 -6
- package/dist/judgement/outcome/predicate-to-expect.d.ts +0 -44
- package/dist/judgement/outcome/predicate-to-expect.js +0 -148
|
@@ -1,115 +1,71 @@
|
|
|
1
|
-
import { bindIntent, declareIntent,
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
1
|
+
import { IntentState, bindIntent, declareIntent, redeclareIntent, dischargeIntent, isActionLabel, } from '@reticlehq/core/artifacts';
|
|
2
|
+
import { IntentShardStore } from './intent-shard-store.js';
|
|
3
|
+
import { IntentStatus } from './intent-shard.js';
|
|
4
4
|
/**
|
|
5
|
-
* The intent ledger
|
|
5
|
+
* The intent ledger's domain operations: declare, place, bind, discharge.
|
|
6
6
|
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
* Every operation goes through the same file lock the other stores use, because two sessions driving
|
|
13
|
-
* one project is ordinary and a lost declaration would be silent.
|
|
7
|
+
* Every operation is a PURE transition from core (`redeclareIntent`, `bindIntent`,
|
|
8
|
+
* `dischargeIntent`) handed to the ledger's atomic upsert. Storage is `IntentShardStore` —
|
|
9
|
+
* `.reticle/intent/<subject>/intent.json`, a flow's name being its subject — and this class writes no
|
|
10
|
+
* file of its own, so there is exactly one place an intent can be.
|
|
14
11
|
*/
|
|
15
|
-
const JSON_INDENT = 2;
|
|
16
12
|
export class IntentStore {
|
|
17
|
-
#
|
|
18
|
-
#root;
|
|
13
|
+
#ledger;
|
|
19
14
|
#clock;
|
|
20
15
|
constructor(fs, root, clock) {
|
|
21
|
-
this.#
|
|
22
|
-
this.#root = root;
|
|
16
|
+
this.#ledger = new IntentShardStore(fs, root, clock);
|
|
23
17
|
this.#clock = clock;
|
|
24
18
|
}
|
|
25
|
-
#path() {
|
|
26
|
-
return reticleDirPaths(this.#root).intent;
|
|
27
|
-
}
|
|
28
|
-
/**
|
|
29
|
-
* Load the ledger, failing soft to empty.
|
|
30
|
-
*
|
|
31
|
-
* This is a git-checked file an agent can write and a human can hand-merge, so a malformed one is
|
|
32
|
-
* genuinely reachable — a conflict marker left in place is the obvious case. Throwing here would
|
|
33
|
-
* take down the verdict that was only asking what was still open, which trades a small problem for
|
|
34
|
-
* a large one.
|
|
35
|
-
*/
|
|
36
|
-
async #load() {
|
|
37
|
-
try {
|
|
38
|
-
const raw = await this.#fs.readFile(this.#path());
|
|
39
|
-
return parseIntentFile(JSON.parse(raw));
|
|
40
|
-
}
|
|
41
|
-
catch {
|
|
42
|
-
return emptyIntentFile();
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
/** Byte-stable: 2-space indent, one trailing newline. An unchanged ledger produces no diff. */
|
|
46
|
-
async #save(file) {
|
|
47
|
-
await this.#fs.mkdir(reticleDirPaths(this.#root).root);
|
|
48
|
-
await this.#fs.writeFile(this.#path(), `${JSON.stringify(file, null, JSON_INDENT)}\n`);
|
|
49
|
-
}
|
|
50
19
|
/** Every intent, in declaration order. */
|
|
51
20
|
async read() {
|
|
52
|
-
return
|
|
21
|
+
return this.#ledger.all();
|
|
53
22
|
}
|
|
54
23
|
/** Everything not yet proved — what an agent asking "am I done?" still owes. */
|
|
55
24
|
async open() {
|
|
56
|
-
|
|
25
|
+
// Stale is not owed: it is kept as history, not as work.
|
|
26
|
+
return (await this.#ledger.all()).filter((record) => IntentState.PROVED !== record.state && IntentStatus.STALE !== record.status);
|
|
57
27
|
}
|
|
58
28
|
/**
|
|
59
29
|
* Declare one or more intents.
|
|
60
30
|
*
|
|
61
31
|
* Batched because the marginal cost of the whole mechanism has to stay at one call per feature; an
|
|
62
|
-
* agent that must make five calls to declare five things will make none.
|
|
32
|
+
* agent that must make five calls to declare five things will make none. Saying an intent again
|
|
33
|
+
* does not unsay it: see `redeclareIntent` for what survives.
|
|
63
34
|
*/
|
|
64
35
|
async declare(entries) {
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
declared.push(stored);
|
|
77
|
-
}
|
|
78
|
-
await this.#save(file);
|
|
79
|
-
return declared;
|
|
80
|
-
});
|
|
36
|
+
const now = this.#clock.now();
|
|
37
|
+
const declared = [];
|
|
38
|
+
for (const entry of entries) {
|
|
39
|
+
// A step label is a log line, not a rule: every route that declares comes through here.
|
|
40
|
+
if (isActionLabel(entry.statement))
|
|
41
|
+
continue;
|
|
42
|
+
const stored = await this.#ledger.upsert(entry.id, (existing) => redeclareIntent(existing, declareIntent({ ...entry, now })));
|
|
43
|
+
if (stored !== undefined)
|
|
44
|
+
declared.push(stored);
|
|
45
|
+
}
|
|
46
|
+
return declared;
|
|
81
47
|
}
|
|
82
48
|
/**
|
|
83
49
|
* File a record under where it turned out to be about.
|
|
84
50
|
*
|
|
85
|
-
* Separate from `declare` because
|
|
86
|
-
*
|
|
87
|
-
*
|
|
88
|
-
* the intent is ABOUT only exists after the consequence lands.
|
|
89
|
-
*
|
|
90
|
-
* Write-once: an existing surface is never overwritten. A record placed by an agent that named its
|
|
91
|
-
* own subject must not be re-filed by a later run that happened to be somewhere else.
|
|
51
|
+
* Separate from `declare` because an inline intent is declared BEFORE the action, and the route
|
|
52
|
+
* that describes it only exists after the consequence lands. Write-once: an existing surface is
|
|
53
|
+
* never overwritten. A flow named here moves the record into that flow's directory.
|
|
92
54
|
*/
|
|
93
55
|
async place(id, surface) {
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
const existing = file.intents[id];
|
|
56
|
+
let placed = false;
|
|
57
|
+
await this.#ledger.upsert(id, (existing) => {
|
|
97
58
|
if (existing === undefined || existing.surface !== undefined)
|
|
98
|
-
return
|
|
99
|
-
|
|
100
|
-
return
|
|
59
|
+
return undefined;
|
|
60
|
+
placed = true;
|
|
61
|
+
return { ...existing, surface };
|
|
101
62
|
});
|
|
63
|
+
return placed;
|
|
102
64
|
}
|
|
103
65
|
/** Attach the predicate that would prove an intent. False when the id names nothing. */
|
|
104
66
|
async bind(id, binding) {
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
const existing = file.intents[id];
|
|
108
|
-
if (existing === undefined)
|
|
109
|
-
return false;
|
|
110
|
-
await this.#save(upsertIntent(file, bindIntent(existing, binding)));
|
|
111
|
-
return true;
|
|
112
|
-
});
|
|
67
|
+
const stored = await this.#ledger.upsert(id, (existing) => existing === undefined ? undefined : bindIntent(existing, binding));
|
|
68
|
+
return stored !== undefined;
|
|
113
69
|
}
|
|
114
70
|
/**
|
|
115
71
|
* Record that a verdict proved an intent.
|
|
@@ -118,16 +74,16 @@ export class IntentStore {
|
|
|
118
74
|
* and must never be the reason one fails to return.
|
|
119
75
|
*/
|
|
120
76
|
async discharge(id, proof) {
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
const existing = file.intents[id];
|
|
77
|
+
let proved = false;
|
|
78
|
+
await this.#ledger.upsert(id, (existing) => {
|
|
124
79
|
if (existing === undefined)
|
|
125
|
-
return
|
|
126
|
-
const
|
|
127
|
-
if (
|
|
128
|
-
return
|
|
129
|
-
|
|
130
|
-
return
|
|
80
|
+
return undefined;
|
|
81
|
+
const next = dischargeIntent(existing, proof);
|
|
82
|
+
if (next === existing)
|
|
83
|
+
return undefined;
|
|
84
|
+
proved = true;
|
|
85
|
+
return next;
|
|
131
86
|
});
|
|
87
|
+
return proved;
|
|
132
88
|
}
|
|
133
89
|
}
|
|
@@ -4,8 +4,10 @@ import { IntentShardStore } from './intent-shard-store.js';
|
|
|
4
4
|
import { IntentStatus } from './intent-shard.js';
|
|
5
5
|
import { ReticleTool } from '@reticlehq/core';
|
|
6
6
|
import { sessionIdShape } from '../../surface/tools/tool-kit.js';
|
|
7
|
+
import { PredicateSchema } from '@reticlehq/engine/question/predicate/predicate.js';
|
|
7
8
|
import { sessionRoot } from '../project/session-root.js';
|
|
8
9
|
import { asString } from '@reticlehq/core';
|
|
10
|
+
import { isActionLabel } from '@reticlehq/core/artifacts';
|
|
9
11
|
/**
|
|
10
12
|
* Declare what a change was SUPPOSED to make true, while somebody still knows.
|
|
11
13
|
*
|
|
@@ -24,11 +26,13 @@ const INDEX = 'index';
|
|
|
24
26
|
const SUBJECT = 'subject';
|
|
25
27
|
const GET = 'get';
|
|
26
28
|
const RECORD = 'record';
|
|
29
|
+
/** Why a step label is not stored, in words that say what to write instead. */
|
|
30
|
+
const STEP_LABEL_REFUSAL = 'describes a step that was done, not what must be true. State the rule, e.g. "saving the form shows the new row in the list"';
|
|
27
31
|
const MIGRATE = 'migrate';
|
|
28
32
|
export const INTENT_TOOLS = [
|
|
29
33
|
{
|
|
30
34
|
name: ReticleTool.INTENT,
|
|
31
|
-
description: 'Record what a change is SUPPOSED to make true, as a durable statement ABOUT THE PRODUCT that a teammate who was not here will understand in six months — name the behaviour, not this run or its step number, and never "renders cleanly", which nothing can check. It is SHARED memory: pooled per project and read back by later agents. Capture it while you still know — then verification does not have to re-derive it from the DOM later. { action:"declare", intents:[{ id, statement, surface? }] } takes prose and needs NO predicate: at declare time there is often no route, no ref and no code yet, and a predicate demanded there is just a mechanism. Declare EARLY (as you build) and batch them — one call per feature is the whole budget. { action:"bind", id, binding } attaches the predicate that would prove it once you know how; an intent with no binding is not a failure, it is the most interesting row in the ledger — something meant that nothing can currently prove. { action:"list" } returns what is still open. Stored in .reticle/intent.json, git-checked so a human sees in review if an intent was later narrowed to match what was easy to prove.',
|
|
35
|
+
description: 'Record what a change is SUPPOSED to make true, as a durable statement ABOUT THE PRODUCT that a teammate who was not here will understand in six months — name the behaviour, not this run or its step number, and never "renders cleanly", which nothing can check. It is SHARED memory: pooled per project and read back by later agents. Capture it while you still know — then verification does not have to re-derive it from the DOM later. { action:"declare", intents:[{ id, statement, surface? }] } takes prose and needs NO predicate: at declare time there is often no route, no ref and no code yet, and a predicate demanded there is just a mechanism. Declare EARLY (as you build) and batch them — one call per feature is the whole budget. { action:"bind", id, binding } attaches the predicate that would prove it once you know how; an intent with no binding is not a failure, it is the most interesting row in the ledger — something meant that nothing can currently prove. { action:"list" } returns what is still open. Stored in .reticle/intent/<subject>/intent.json (a flow name is its subject), git-checked so a human sees in review if an intent was later narrowed to match what was easy to prove.',
|
|
32
36
|
example: {
|
|
33
37
|
action: DECLARE,
|
|
34
38
|
intents: [
|
|
@@ -56,8 +60,8 @@ export const INTENT_TOOLS = [
|
|
|
56
60
|
.describe('record only: how settled it is. Defaults to proposed.'),
|
|
57
61
|
intents: z
|
|
58
62
|
.array(z.object({
|
|
59
|
-
id: z.string(),
|
|
60
|
-
statement: z.string(),
|
|
63
|
+
id: z.string().min(1),
|
|
64
|
+
statement: z.string().min(1),
|
|
61
65
|
surface: z
|
|
62
66
|
.object({
|
|
63
67
|
route: z.string().optional(),
|
|
@@ -69,10 +73,19 @@ export const INTENT_TOOLS = [
|
|
|
69
73
|
.optional()
|
|
70
74
|
.describe('declare only. Batchable — declare every intent for a feature in one call.'),
|
|
71
75
|
id: z.string().optional().describe('bind only: which intent the predicate proves.'),
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
+
/*
|
|
77
|
+
* The predicate schema, not `unknown`.
|
|
78
|
+
*
|
|
79
|
+
* The description has always said "in reticle_assert's shape" and the type said "anything",
|
|
80
|
+
* so a client reading the surface to build this call learned the field's name and nothing
|
|
81
|
+
* else — the same degradation the recursive `until` predicate suffered when it was converted
|
|
82
|
+
* without the SDK's own options. This one was not a converter bug: it was declared that way.
|
|
83
|
+
* Caught by `weakest-client-schemas.test.ts`, which is the first thing that ever looked.
|
|
84
|
+
*
|
|
85
|
+
* Accepting the real shape also means a malformed binding is refused at the boundary instead
|
|
86
|
+
* of being written into the ledger and failing later against a verdict it can never satisfy.
|
|
87
|
+
*/
|
|
88
|
+
binding: PredicateSchema.optional().describe("bind only: the predicate that would prove it, in reticle_assert's shape."),
|
|
76
89
|
...sessionIdShape,
|
|
77
90
|
},
|
|
78
91
|
outputSchema: {
|
|
@@ -81,6 +94,10 @@ export const INTENT_TOOLS = [
|
|
|
81
94
|
.optional()
|
|
82
95
|
.describe('The intents this call declared, or on `list` everything still open — each { id, statement, state, declaredAt, binding?, surface?, provenBy?, amended? }. `state` is declared (prose only), bound (a predicate exists), or proved (a verdict satisfied it).'),
|
|
83
96
|
bound: z.boolean().optional().describe('bind only: false when the id names no intent.'),
|
|
97
|
+
refused: z
|
|
98
|
+
.array(z.object({ id: z.string(), reason: z.string() }))
|
|
99
|
+
.optional()
|
|
100
|
+
.describe('declare only: statements not stored, and why.'),
|
|
84
101
|
path: z.string().optional().describe('Where the ledger was written.'),
|
|
85
102
|
entries: z
|
|
86
103
|
.array(z.unknown())
|
|
@@ -130,28 +147,22 @@ export const INTENT_TOOLS = [
|
|
|
130
147
|
return { bound: await store.bind(id, args['binding']) };
|
|
131
148
|
}
|
|
132
149
|
if (LIST === action) {
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
* `.reticle/intent/`. Listing only the flat one meant a recorded intent could not be found
|
|
138
|
-
* again by the agent that had just written it — and the lesson an agent draws from that is
|
|
139
|
-
* "the intent does not exist", not "there are two stores".
|
|
140
|
-
*
|
|
141
|
-
* Merged HERE rather than inside either store: this tool is the one seam that knows both
|
|
142
|
-
* exist, and neither store should have to learn the other's layout to stay honest.
|
|
143
|
-
*/
|
|
144
|
-
const flat = await store.open();
|
|
145
|
-
const seen = new Set(flat.map((intent) => intent.id));
|
|
146
|
-
const fromShards = (await shards.all()).filter((record) => !seen.has(record.id));
|
|
147
|
-
return { intents: [...flat, ...fromShards] };
|
|
150
|
+
// One ledger, so one read. This merged two stores while `declare` and `record` wrote two
|
|
151
|
+
// different files; kept past that, it would add back every PROVED intent to a list that
|
|
152
|
+
// promises only what is still open.
|
|
153
|
+
return { intents: await store.open() };
|
|
148
154
|
}
|
|
149
155
|
const raw = args['intents'];
|
|
150
156
|
const entries = Array.isArray(raw)
|
|
151
157
|
? raw
|
|
152
158
|
: [];
|
|
153
159
|
const declared = await store.declare(entries);
|
|
154
|
-
|
|
160
|
+
// Said, not swallowed: an agent that declared something and finds nothing stored would
|
|
161
|
+
// otherwise conclude the ledger lost it.
|
|
162
|
+
const refused = entries
|
|
163
|
+
.filter((entry) => isActionLabel(entry.statement))
|
|
164
|
+
.map((entry) => ({ id: entry.id, reason: STEP_LABEL_REFUSAL }));
|
|
165
|
+
return { intents: declared, ...(0 === refused.length ? {} : { refused }), path: root };
|
|
155
166
|
},
|
|
156
167
|
},
|
|
157
168
|
];
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { dirname } from 'node:path';
|
|
2
|
+
import { writeFileAtomic } from '../project/fs/write-atomic.js';
|
|
2
3
|
import { reticleDirPaths } from '../project/dir/reticle-dir.js';
|
|
3
4
|
import { AmbientFileSchema } from './on-disk/ambient-file.js';
|
|
4
5
|
import { onlyStableAmbient } from '@reticlehq/engine/window/ambient.js';
|
|
@@ -45,8 +46,6 @@ export class AmbientStore {
|
|
|
45
46
|
// numbering, so persisting it teaches the next session to suppress an element it never saw.
|
|
46
47
|
const regions = onlyStableAmbient(counts);
|
|
47
48
|
const body = `${JSON.stringify({ version: AMBIENT_FILE_VERSION, regions }, null, 2)}\n`;
|
|
48
|
-
|
|
49
|
-
await this.#fs.writeFile(tmp, body);
|
|
50
|
-
await this.#fs.rename(tmp, this.#path);
|
|
49
|
+
await writeFileAtomic(this.#fs, this.#path, body);
|
|
51
50
|
}
|
|
52
51
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { routeTemplate } from './route-template.js';
|
|
1
2
|
import { compareSegment, MIN_ENVELOPE_SAMPLES, } from './envelope.js';
|
|
2
3
|
function headlineFor(report) {
|
|
3
4
|
const truncNote = report.truncatedSegments > 0
|
|
@@ -31,7 +32,9 @@ export function buildDeviationReport(envelopes, segments, zThreshold) {
|
|
|
31
32
|
truncated += 1;
|
|
32
33
|
continue;
|
|
33
34
|
}
|
|
34
|
-
|
|
35
|
+
// The SAME key the service accumulates under — a lookup on the observed path would miss every
|
|
36
|
+
// envelope on an app with ids in its URLs, which is the case this templating exists for.
|
|
37
|
+
const envelope = envelopes.get(routeTemplate(segment.route));
|
|
35
38
|
if (envelope === undefined || envelope.samples < MIN_ENVELOPE_SAMPLES)
|
|
36
39
|
continue;
|
|
37
40
|
judged += 1;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { routeTemplate } from './route-template.js';
|
|
1
2
|
import { addSegmentToEnvelope, emptyEnvelope } from './envelope.js';
|
|
2
3
|
import { buildDeviationReport } from './deviation-report.js';
|
|
3
4
|
import { withFileLock } from '../project/file-lock.js';
|
|
@@ -22,8 +23,15 @@ export async function reportAndAccumulate(store, segments, zThreshold) {
|
|
|
22
23
|
// Never learn from a truncated sample — its understated counts would poison the baseline.
|
|
23
24
|
if (true === segment.truncated)
|
|
24
25
|
continue;
|
|
25
|
-
|
|
26
|
-
|
|
26
|
+
/*
|
|
27
|
+
* Keyed on the route TEMPLATE, not the observed path. Keyed on the path, an app with ids in
|
|
28
|
+
* its URLs mints one envelope per id, every envelope holds a single sample, and nothing ever
|
|
29
|
+
* reaches MIN_ENVELOPE_SAMPLES — so the deviation report answers "envelope too new" forever
|
|
30
|
+
* and the feature never turns on. See route-template.ts.
|
|
31
|
+
*/
|
|
32
|
+
const key = routeTemplate(segment.route);
|
|
33
|
+
const current = envelopes.get(key) ?? emptyEnvelope(key);
|
|
34
|
+
envelopes.set(key, addSegmentToEnvelope(current, segment));
|
|
27
35
|
}
|
|
28
36
|
await store.save(envelopes);
|
|
29
37
|
return report;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { FlowName, Predicate } from '@reticlehq/core';
|
|
2
2
|
/**
|
|
3
3
|
* What this module needs a recorded step to BE, named structurally rather than imported.
|
|
4
4
|
*
|
|
@@ -12,7 +12,7 @@ export interface TapeStep {
|
|
|
12
12
|
tool: string;
|
|
13
13
|
args: Record<string, unknown>;
|
|
14
14
|
stable: boolean;
|
|
15
|
-
expect?:
|
|
15
|
+
expect?: Predicate;
|
|
16
16
|
/** Recorder-internal, and the field journeys are cut on. Never written to the saved flow. */
|
|
17
17
|
route?: string;
|
|
18
18
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { REPLAY_PROGRAM_VERSION, asFlowName } from '@reticlehq/core';
|
|
1
|
+
import { PredicateKind, REPLAY_PROGRAM_VERSION, asFlowName } from '@reticlehq/core';
|
|
2
|
+
import { clauseOfKind } from '@reticlehq/core';
|
|
2
3
|
/**
|
|
3
4
|
* What a session drove, saved as a replayable flow — without anybody asking for it.
|
|
4
5
|
*
|
|
@@ -36,21 +37,29 @@ function provedSlug(steps) {
|
|
|
36
37
|
const expect = step.expect;
|
|
37
38
|
if (expect === undefined)
|
|
38
39
|
continue;
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
40
|
+
// A predicate names its kind, so the slug reads the tree rather than probing a struct. The
|
|
41
|
+
// signal wins over the request for the same reason it always did: it is the app saying what it
|
|
42
|
+
// did, which is the strongest thing a name can carry.
|
|
43
|
+
const signal = clauseOfKind(expect, PredicateKind.SIGNAL);
|
|
44
|
+
if (signal?.name !== undefined)
|
|
45
|
+
return segment(signal.name, 32);
|
|
46
|
+
const netClause = clauseOfKind(expect, PredicateKind.NET);
|
|
47
|
+
if (netClause !== undefined) {
|
|
48
|
+
const method = segment(netClause.method ?? '', 6);
|
|
49
|
+
const where = segment(netClause.urlContains ?? '', 24);
|
|
44
50
|
const joined = [method, where].filter((part) => '' !== part).join('-');
|
|
45
51
|
if ('' !== joined)
|
|
46
52
|
return joined;
|
|
47
53
|
}
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
if (
|
|
53
|
-
return segment(
|
|
54
|
+
const state = clauseOfKind(expect, PredicateKind.STATE);
|
|
55
|
+
if (state !== undefined)
|
|
56
|
+
return segment(state.path, 32);
|
|
57
|
+
const route = clauseOfKind(expect, PredicateKind.ROUTE);
|
|
58
|
+
if (route?.pathname !== undefined)
|
|
59
|
+
return segment(route.pathname, 24);
|
|
60
|
+
const text = clauseOfKind(expect, PredicateKind.TEXT);
|
|
61
|
+
if (text?.contains !== undefined)
|
|
62
|
+
return segment(text.contains, 24);
|
|
54
63
|
}
|
|
55
64
|
return '';
|
|
56
65
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
|
+
import { writeFileAtomic } from '../project/fs/write-atomic.js';
|
|
2
3
|
import { dirname } from 'node:path';
|
|
3
4
|
import { reticleDirPaths } from '../project/dir/reticle-dir.js';
|
|
4
5
|
import { RouteEnvelopeSchema } from './envelope.js';
|
|
@@ -54,8 +55,6 @@ export class EnvelopeStore {
|
|
|
54
55
|
routes[route] = envelope;
|
|
55
56
|
const body = `${JSON.stringify({ version: ENVELOPE_FILE_VERSION, routes }, null, 2)}\n`;
|
|
56
57
|
// Atomic publish: write a temp sibling then rename, so a crash never leaves a half-written baseline.
|
|
57
|
-
|
|
58
|
-
await this.#fs.writeFile(tmp, body);
|
|
59
|
-
await this.#fs.rename(tmp, this.#path);
|
|
58
|
+
await writeFileAtomic(this.#fs, this.#path, body);
|
|
60
59
|
}
|
|
61
60
|
}
|
|
@@ -53,6 +53,27 @@ export interface JournalReader {
|
|
|
53
53
|
* only ever about writes that never happened.
|
|
54
54
|
*/
|
|
55
55
|
readWriteLoss?(): Promise<JournalWriteLoss | undefined>;
|
|
56
|
+
/**
|
|
57
|
+
* How big the event ledger is right now, and the ceiling it is measured against.
|
|
58
|
+
*
|
|
59
|
+
* Synchronous and cached: the size is already tracked for the cap, so reporting it costs a field
|
|
60
|
+
* read. Optional on the same reasoning as `readActions` -- a partial double declares no size,
|
|
61
|
+
* and a caller reads that as "not measured" rather than "empty".
|
|
62
|
+
*
|
|
63
|
+
* It is reported at all because nothing reported it. A repeating uncaught error wrote one
|
|
64
|
+
* session's ledger without limit, and neither the daemon nor
|
|
65
|
+
* `doctor` nor any health counter said a word; the user found out when the disk was full (#986).
|
|
66
|
+
* The cap that now bounds this is only half an answer -- a ledger silently approaching it is
|
|
67
|
+
* still a dev tool writing hard to somebody's repo directory.
|
|
68
|
+
*/
|
|
69
|
+
ledger?(): JournalLedgerSize | undefined;
|
|
70
|
+
}
|
|
71
|
+
/** The event ledger's size on disk against the ceiling that will refuse it. */
|
|
72
|
+
export interface JournalLedgerSize {
|
|
73
|
+
/** Bytes of `events.jsonl` on disk. */
|
|
74
|
+
bytes: number;
|
|
75
|
+
/** The ceiling that refuses the next batch past it. */
|
|
76
|
+
capBytes: number;
|
|
56
77
|
}
|
|
57
78
|
interface JournalRecorderOptions {
|
|
58
79
|
/** Injected elapsed-ms clock (never read from Date here — the clock-injection rule). */
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What a project is willing to keep.
|
|
3
|
+
*
|
|
4
|
+
* The only answer `.reticle.json` used to offer a full directory was `journal: false` — turning off
|
|
5
|
+
* the thing the product is FOR, and, until the sweep was ungated, the one setting under which
|
|
6
|
+
* nothing was deleted either. The bounds themselves were four constants no user could reach.
|
|
7
|
+
*
|
|
8
|
+
* `0` is a real answer and means keep none, which the count bounds already do for free: a sweep that
|
|
9
|
+
* keeps zero directories deletes all of them. It is not the same as "unset", which is why every
|
|
10
|
+
* field is read explicitly rather than with `??` over a falsy value.
|
|
11
|
+
*/
|
|
12
|
+
export interface RetainPolicy {
|
|
13
|
+
/** Session journal directories under `sessions/`. */
|
|
14
|
+
sessions: number;
|
|
15
|
+
/** Overlay diffs under `visual/`. Baselines are memory and are never counted here. */
|
|
16
|
+
visual: number;
|
|
17
|
+
/** Local copies of reports the outbox already carries. */
|
|
18
|
+
feedback: number;
|
|
19
|
+
/** The TOTAL the evidence tier may occupy, in bytes. Stated in the file as `budgetMb`. */
|
|
20
|
+
budgetBytes: number;
|
|
21
|
+
}
|
|
22
|
+
export declare const DEFAULT_RETAIN: RetainPolicy;
|
|
23
|
+
/**
|
|
24
|
+
* Read the `retain` block out of an already-parsed `.reticle.json`.
|
|
25
|
+
*
|
|
26
|
+
* Takes the record rather than a path: the config search belongs to the caller that already does it
|
|
27
|
+
* once at startup, and reaching for it from here would point the retention domain at the
|
|
28
|
+
* command-line surface for a single lookup.
|
|
29
|
+
*
|
|
30
|
+
* Never throws, and falls back PER FIELD. A project that mistypes one bound keeps the other three —
|
|
31
|
+
* the alternative silently reverts every bound the user did set, which is the same class of quiet
|
|
32
|
+
* disagreement the whole partition of `.reticle/` exists to prevent.
|
|
33
|
+
*/
|
|
34
|
+
export declare function readRetainPolicy(config: Record<string, unknown> | undefined): RetainPolicy;
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { DEFAULT_DIFF_RETENTION, DEFAULT_EVIDENCE_BUDGET_BYTES, DEFAULT_FEEDBACK_RETENTION, DEFAULT_SESSION_RETENTION, } from './retention.js';
|
|
2
|
+
/** The key a project writes in `.reticle.json`. */
|
|
3
|
+
const RETAIN_KEY = 'retain';
|
|
4
|
+
const BYTES_PER_MB = 1024 * 1024;
|
|
5
|
+
export const DEFAULT_RETAIN = {
|
|
6
|
+
sessions: DEFAULT_SESSION_RETENTION,
|
|
7
|
+
visual: DEFAULT_DIFF_RETENTION,
|
|
8
|
+
feedback: DEFAULT_FEEDBACK_RETENTION,
|
|
9
|
+
budgetBytes: DEFAULT_EVIDENCE_BUDGET_BYTES,
|
|
10
|
+
};
|
|
11
|
+
/**
|
|
12
|
+
* A count a sweep can act on, or `undefined` for anything else.
|
|
13
|
+
*
|
|
14
|
+
* Negative and fractional are refused rather than coerced. "Keep -1 sessions" and "keep 1.5" are
|
|
15
|
+
* both a user saying something they did not mean, and a sweep that guesses at what they meant is
|
|
16
|
+
* deleting files on a guess.
|
|
17
|
+
*/
|
|
18
|
+
function count(value) {
|
|
19
|
+
return 'number' === typeof value && Number.isInteger(value) && value >= 0 ? value : undefined;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Read the `retain` block out of an already-parsed `.reticle.json`.
|
|
23
|
+
*
|
|
24
|
+
* Takes the record rather than a path: the config search belongs to the caller that already does it
|
|
25
|
+
* once at startup, and reaching for it from here would point the retention domain at the
|
|
26
|
+
* command-line surface for a single lookup.
|
|
27
|
+
*
|
|
28
|
+
* Never throws, and falls back PER FIELD. A project that mistypes one bound keeps the other three —
|
|
29
|
+
* the alternative silently reverts every bound the user did set, which is the same class of quiet
|
|
30
|
+
* disagreement the whole partition of `.reticle/` exists to prevent.
|
|
31
|
+
*/
|
|
32
|
+
export function readRetainPolicy(config) {
|
|
33
|
+
const raw = config?.[RETAIN_KEY];
|
|
34
|
+
if ('object' !== typeof raw || null === raw || Array.isArray(raw))
|
|
35
|
+
return DEFAULT_RETAIN;
|
|
36
|
+
const retain = raw;
|
|
37
|
+
const mb = count(retain['budgetMb']);
|
|
38
|
+
return {
|
|
39
|
+
sessions: count(retain['sessions']) ?? DEFAULT_RETAIN.sessions,
|
|
40
|
+
visual: count(retain['visual']) ?? DEFAULT_RETAIN.visual,
|
|
41
|
+
feedback: count(retain['feedback']) ?? DEFAULT_RETAIN.feedback,
|
|
42
|
+
budgetBytes: mb === undefined ? DEFAULT_RETAIN.budgetBytes : mb * BYTES_PER_MB,
|
|
43
|
+
};
|
|
44
|
+
}
|
|
@@ -16,9 +16,10 @@ export declare const DEFAULT_FEEDBACK_RETENTION = 20;
|
|
|
16
16
|
* and feedback copies together.
|
|
17
17
|
*
|
|
18
18
|
* Every bound above this line counts things, and a count cannot see a size: twenty session
|
|
19
|
-
* directories is twenty UNBOUNDED directories. A
|
|
20
|
-
* three counts being honoured the whole time, because one drive against a chatty app
|
|
21
|
-
* response bodies and DOM text for as long as the drive lasts, and nothing was looking at
|
|
19
|
+
* directories is twenty UNBOUNDED directories. A workspace in the field grew to multiple gigabytes
|
|
20
|
+
* with all three counts being honoured the whole time, because one drive against a chatty app
|
|
21
|
+
* appends response bodies and DOM text for as long as the drive lasts, and nothing was looking at
|
|
22
|
+
* the total.
|
|
22
23
|
*
|
|
23
24
|
* Why this number and not one an order of magnitude either side. An ordinary session journal is
|
|
24
25
|
* hundreds of kilobytes to a few megabytes; a long drive against an app with large JSON responses
|
|
@@ -40,11 +41,11 @@ export declare const DEFAULT_EVIDENCE_BUDGET_BYTES: number;
|
|
|
40
41
|
/**
|
|
41
42
|
* One candidate entry inside a `.reticle/`, classified by the top-level directory it lives under.
|
|
42
43
|
*
|
|
43
|
-
* `under` is what decides the tier, and it is
|
|
44
|
-
*
|
|
45
|
-
*
|
|
46
|
-
*
|
|
47
|
-
*
|
|
44
|
+
* `under` is what decides the tier, and it is looked up in the workspace tier table's `durability`
|
|
45
|
+
* axis — never in the gitignore's, which answers a different question. Anything not EVIDENCE is
|
|
46
|
+
* MEMORY: flows, capsules, baselines, the contract, the intent ledger. Those are small, durable
|
|
47
|
+
* and the reason the directory exists, so they are never evicted, never counted, and must keep
|
|
48
|
+
* being written however full the disk is.
|
|
48
49
|
*/
|
|
49
50
|
export interface TierEntry {
|
|
50
51
|
/** Absolute path, and what is removed. */
|
|
@@ -73,8 +74,6 @@ export declare function selectPrunable(dirs: readonly DatedDir[], retention: num
|
|
|
73
74
|
export interface RetentionOptions {
|
|
74
75
|
/** How many session directories survive the count bound. */
|
|
75
76
|
retention?: number;
|
|
76
|
-
/** The TOTAL the evidence tier may occupy. */
|
|
77
|
-
budgetBytes?: number;
|
|
78
77
|
/**
|
|
79
78
|
* The ids of sessions that are STILL OPEN. Neither bound may evict one.
|
|
80
79
|
*
|
|
@@ -94,6 +93,11 @@ export interface RetentionOptions {
|
|
|
94
93
|
* so a stat/rm failure on one dir (or a missing sessions/ dir) is swallowed, never crashing a session.
|
|
95
94
|
* Mirrors RunStore's amortized pruning; run on daemon start and at the end of every session.
|
|
96
95
|
*
|
|
96
|
+
* The COUNT bound only. This used to sweep the tier total as well, from when it was the single
|
|
97
|
+
* sweep both call sites used; `pruneWorkspace` is that single place now and runs the budget last,
|
|
98
|
+
* on purpose, so the leftover call here was a second walk of every evidence entry on every teardown
|
|
99
|
+
* — and it ran FIRST, sizing everything the counts were about to delete anyway.
|
|
100
|
+
*
|
|
97
101
|
* An open session is excluded from BOTH bounds — see `RetentionOptions.live`.
|
|
98
102
|
*
|
|
99
103
|
* ponytail: excluded via `keep`, so an open session is not COUNTED toward the retention cap either.
|