@llblab/pi-kit 0.18.2 → 0.19.1

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.
Files changed (113) hide show
  1. package/CHANGELOG.md +11 -0
  2. package/README.md +3 -1
  3. package/node_modules/@llblab/pi-state-flow/AGENTS.md +35 -37
  4. package/node_modules/@llblab/pi-state-flow/BACKLOG.md +5 -3
  5. package/node_modules/@llblab/pi-state-flow/CHANGELOG.md +23 -1
  6. package/node_modules/@llblab/pi-state-flow/README.md +102 -48
  7. package/node_modules/@llblab/pi-state-flow/dist/index.d.ts +6 -9
  8. package/node_modules/@llblab/pi-state-flow/dist/index.js +6 -9
  9. package/node_modules/@llblab/pi-state-flow/dist/lib/acquisition.d.ts +2 -2
  10. package/node_modules/@llblab/pi-state-flow/dist/lib/acquisition.js +21 -8
  11. package/node_modules/@llblab/pi-state-flow/dist/lib/artifact.d.ts +40 -24
  12. package/node_modules/@llblab/pi-state-flow/dist/lib/artifact.js +93 -63
  13. package/node_modules/@llblab/pi-state-flow/dist/lib/compaction.d.ts +4 -3
  14. package/node_modules/@llblab/pi-state-flow/dist/lib/compaction.js +9 -9
  15. package/node_modules/@llblab/pi-state-flow/dist/lib/config.d.ts +1 -1
  16. package/node_modules/@llblab/pi-state-flow/dist/lib/config.js +6 -5
  17. package/node_modules/@llblab/pi-state-flow/dist/lib/context.d.ts +8 -7
  18. package/node_modules/@llblab/pi-state-flow/dist/lib/context.js +56 -58
  19. package/node_modules/@llblab/pi-state-flow/dist/lib/continuation.js +12 -24
  20. package/node_modules/@llblab/pi-state-flow/dist/lib/durable.d.ts +7 -18
  21. package/node_modules/@llblab/pi-state-flow/dist/lib/durable.js +47 -78
  22. package/node_modules/@llblab/pi-state-flow/dist/lib/extension.d.ts +4 -6
  23. package/node_modules/@llblab/pi-state-flow/dist/lib/extension.js +241 -437
  24. package/node_modules/@llblab/pi-state-flow/dist/lib/git.d.ts +2 -72
  25. package/node_modules/@llblab/pi-state-flow/dist/lib/git.js +120 -499
  26. package/node_modules/@llblab/pi-state-flow/dist/lib/history.d.ts +2 -1
  27. package/node_modules/@llblab/pi-state-flow/dist/lib/history.js +4 -3
  28. package/node_modules/@llblab/pi-state-flow/dist/lib/json.d.ts +3 -0
  29. package/node_modules/@llblab/pi-state-flow/dist/lib/json.js +43 -22
  30. package/node_modules/@llblab/pi-state-flow/dist/lib/logging.d.ts +1 -5
  31. package/node_modules/@llblab/pi-state-flow/dist/lib/logging.js +0 -2
  32. package/node_modules/@llblab/pi-state-flow/dist/lib/memory.d.ts +1 -14
  33. package/node_modules/@llblab/pi-state-flow/dist/lib/memory.js +5 -37
  34. package/node_modules/@llblab/pi-state-flow/dist/lib/protocol.d.ts +1 -2
  35. package/node_modules/@llblab/pi-state-flow/dist/lib/protocol.js +13 -32
  36. package/node_modules/@llblab/pi-state-flow/dist/lib/query.d.ts +6 -6
  37. package/node_modules/@llblab/pi-state-flow/dist/lib/query.js +25 -21
  38. package/node_modules/@llblab/pi-state-flow/dist/lib/recovery.d.ts +3 -3
  39. package/node_modules/@llblab/pi-state-flow/dist/lib/recovery.js +15 -12
  40. package/node_modules/@llblab/pi-state-flow/dist/lib/rehydration.d.ts +2 -4
  41. package/node_modules/@llblab/pi-state-flow/dist/lib/rehydration.js +5 -1
  42. package/node_modules/@llblab/pi-state-flow/dist/lib/runtime.d.ts +26 -88
  43. package/node_modules/@llblab/pi-state-flow/dist/lib/runtime.js +160 -255
  44. package/node_modules/@llblab/pi-state-flow/dist/lib/skills.d.ts +0 -3
  45. package/node_modules/@llblab/pi-state-flow/dist/lib/skills.js +1 -47
  46. package/node_modules/@llblab/pi-state-flow/dist/lib/snapshot.d.ts +16 -33
  47. package/node_modules/@llblab/pi-state-flow/dist/lib/snapshot.js +48 -137
  48. package/node_modules/@llblab/pi-state-flow/dist/lib/state.d.ts +16 -11
  49. package/node_modules/@llblab/pi-state-flow/dist/lib/state.js +13 -14
  50. package/node_modules/@llblab/pi-state-flow/dist/lib/status.d.ts +1 -9
  51. package/node_modules/@llblab/pi-state-flow/dist/lib/status.js +15 -43
  52. package/node_modules/@llblab/pi-state-flow/dist/lib/storage.d.ts +5 -9
  53. package/node_modules/@llblab/pi-state-flow/dist/lib/storage.js +13 -45
  54. package/node_modules/@llblab/pi-state-flow/dist/lib/telegram.js +1 -1
  55. package/node_modules/@llblab/pi-state-flow/dist/lib/temporal.d.ts +15 -7
  56. package/node_modules/@llblab/pi-state-flow/dist/lib/temporal.js +104 -24
  57. package/node_modules/@llblab/pi-state-flow/dist/lib/transition.d.ts +0 -2
  58. package/node_modules/@llblab/pi-state-flow/dist/lib/transition.js +11 -14
  59. package/node_modules/@llblab/pi-state-flow/dist/package.json +9 -6
  60. package/node_modules/@llblab/pi-state-flow/dist/skills/state-flow-guide/SKILL.md +11 -17
  61. package/node_modules/@llblab/pi-state-flow/dist/skills/state-flow-memory/SKILL.md +8 -8
  62. package/node_modules/@llblab/pi-state-flow/docs/README.md +2 -2
  63. package/node_modules/@llblab/pi-state-flow/docs/architecture.md +64 -67
  64. package/node_modules/@llblab/pi-state-flow/docs/compatibility.md +24 -6
  65. package/node_modules/@llblab/pi-state-flow/docs/filesystem-recovery.md +12 -15
  66. package/node_modules/@llblab/pi-state-flow/docs/fork-contract.md +22 -27
  67. package/node_modules/@llblab/pi-state-flow/docs/lazy-state.md +25 -41
  68. package/node_modules/@llblab/pi-state-flow/docs/performance.md +38 -421
  69. package/node_modules/@llblab/pi-state-flow/docs/temporal-acceptance.md +33 -46
  70. package/node_modules/@llblab/pi-state-flow/docs/usage.md +37 -61
  71. package/node_modules/@llblab/pi-state-flow/index.ts +7 -71
  72. package/node_modules/@llblab/pi-state-flow/lib/acquisition.ts +26 -11
  73. package/node_modules/@llblab/pi-state-flow/lib/artifact.ts +116 -88
  74. package/node_modules/@llblab/pi-state-flow/lib/compaction.ts +12 -10
  75. package/node_modules/@llblab/pi-state-flow/lib/config.ts +6 -6
  76. package/node_modules/@llblab/pi-state-flow/lib/context.ts +52 -59
  77. package/node_modules/@llblab/pi-state-flow/lib/continuation.ts +11 -20
  78. package/node_modules/@llblab/pi-state-flow/lib/durable.ts +44 -86
  79. package/node_modules/@llblab/pi-state-flow/lib/extension.ts +237 -460
  80. package/node_modules/@llblab/pi-state-flow/lib/git.ts +110 -552
  81. package/node_modules/@llblab/pi-state-flow/lib/history.ts +4 -3
  82. package/node_modules/@llblab/pi-state-flow/lib/json.ts +39 -23
  83. package/node_modules/@llblab/pi-state-flow/lib/logging.ts +1 -7
  84. package/node_modules/@llblab/pi-state-flow/lib/memory.ts +5 -44
  85. package/node_modules/@llblab/pi-state-flow/lib/protocol.ts +14 -25
  86. package/node_modules/@llblab/pi-state-flow/lib/query.ts +26 -22
  87. package/node_modules/@llblab/pi-state-flow/lib/recovery.ts +17 -11
  88. package/node_modules/@llblab/pi-state-flow/lib/rehydration.ts +7 -5
  89. package/node_modules/@llblab/pi-state-flow/lib/runtime.ts +155 -254
  90. package/node_modules/@llblab/pi-state-flow/lib/skills.ts +1 -49
  91. package/node_modules/@llblab/pi-state-flow/lib/snapshot.ts +58 -142
  92. package/node_modules/@llblab/pi-state-flow/lib/state.ts +27 -19
  93. package/node_modules/@llblab/pi-state-flow/lib/status.ts +16 -54
  94. package/node_modules/@llblab/pi-state-flow/lib/storage.ts +12 -40
  95. package/node_modules/@llblab/pi-state-flow/lib/telegram.ts +1 -1
  96. package/node_modules/@llblab/pi-state-flow/lib/temporal.ts +104 -22
  97. package/node_modules/@llblab/pi-state-flow/lib/transition.ts +16 -26
  98. package/node_modules/@llblab/pi-state-flow/package.json +9 -6
  99. package/node_modules/@llblab/pi-state-flow/skills/state-flow-guide/SKILL.md +11 -17
  100. package/node_modules/@llblab/pi-state-flow/skills/state-flow-memory/SKILL.md +8 -8
  101. package/package.json +2 -2
  102. package/node_modules/@llblab/pi-state-flow/dist/lib/discovery.d.ts +0 -21
  103. package/node_modules/@llblab/pi-state-flow/dist/lib/discovery.js +0 -125
  104. package/node_modules/@llblab/pi-state-flow/dist/lib/maintenance.d.ts +0 -36
  105. package/node_modules/@llblab/pi-state-flow/dist/lib/maintenance.js +0 -98
  106. package/node_modules/@llblab/pi-state-flow/dist/lib/migration.d.ts +0 -13
  107. package/node_modules/@llblab/pi-state-flow/dist/lib/migration.js +0 -167
  108. package/node_modules/@llblab/pi-state-flow/dist/lib/publication.d.ts +0 -86
  109. package/node_modules/@llblab/pi-state-flow/dist/lib/publication.js +0 -437
  110. package/node_modules/@llblab/pi-state-flow/lib/discovery.ts +0 -133
  111. package/node_modules/@llblab/pi-state-flow/lib/maintenance.ts +0 -147
  112. package/node_modules/@llblab/pi-state-flow/lib/migration.ts +0 -171
  113. package/node_modules/@llblab/pi-state-flow/lib/publication.ts +0 -458
@@ -2,12 +2,10 @@ import { readFileSync } from "node:fs";
2
2
  import {
3
3
  hashArtifactSource,
4
4
  isArtifactHash,
5
- type ArtifactMetadata,
6
5
  type ArtifactProvenance,
7
6
  type ArtifactRegistry,
8
7
  } from "./artifact.ts";
9
- import { canonicalJson, isObject, type JsonObject, type JsonValue } from "./json.ts";
10
- import type { MaterializedState } from "./state.ts";
8
+ import { isObject } from "./json.ts";
11
9
 
12
10
  export const SKILL_ARTIFACT_COMPILER = "skill-artifact-v1";
13
11
 
@@ -46,52 +44,6 @@ export function hashSkillSource(source: string): string {
46
44
  return hashArtifactSource(readFileSync(source));
47
45
  }
48
46
 
49
- function legacyCompilation(value: JsonValue): JsonObject | undefined {
50
- if (!hasContent(value)) return undefined;
51
- return isObject(value) ? structuredClone(value) : { value: structuredClone(value) };
52
- }
53
-
54
- /** Move the retired contract store into source-addressed Skill artifacts. */
55
- export function migrateLegacySkillCompilations(
56
- state: MaterializedState,
57
- hasher: SkillSourceHasher = hashSkillSource,
58
- ): MaterializedState {
59
- if (!isObject(state.contract.compiled_skills)) return structuredClone(state);
60
- const legacy = state.contract.compiled_skills;
61
- const next = structuredClone(state);
62
- delete next.contract.compiled_skills;
63
- for (const [source, value] of Object.entries(legacy)) {
64
- const compilation = legacyCompilation(value);
65
- if (!compilation) continue;
66
- let hash: string;
67
- let verified = true;
68
- try {
69
- hash = hasher(source);
70
- if (!isArtifactHash(hash)) throw new Error("invalid source hash");
71
- } catch {
72
- // Preserve useful legacy behavior while ensuring a later observable source
73
- // identity normally invalidates this explicitly unverified fallback.
74
- hash = hashArtifactSource(canonicalJson({ source, compilation }));
75
- verified = false;
76
- }
77
- const metadata: ArtifactMetadata = {
78
- description: `Compiled operational guidance for the Skill at ${source}`,
79
- hash,
80
- compiler: SKILL_ARTIFACT_COMPILER,
81
- kind: "skill",
82
- compilation,
83
- ...(verified ? {} : { source_hash_verified: false }),
84
- };
85
- Object.defineProperty(next.artifacts, source, {
86
- value: metadata,
87
- enumerable: true,
88
- configurable: true,
89
- writable: true,
90
- });
91
- }
92
- return next;
93
- }
94
-
95
47
  export function skillPathFromRead(toolName: unknown, args: unknown): string | undefined {
96
48
  if (toolName !== "read" || !isObject(args) || typeof args.path !== "string") return undefined;
97
49
  return /(^|[\\/])SKILL\.md$/.test(args.path) ? args.path : undefined;
@@ -1,9 +1,8 @@
1
1
  import { resolve } from "node:path";
2
2
  import { parseArtifactProvenanceRegistry, type ArtifactProvenanceRegistry } from "./artifact.ts";
3
- import { parseRemotePublicationPolicyDocument, serializeRemotePublicationPolicyDocument, type RemotePublicationPolicyDocument } from "./publication.ts";
3
+ import { MAX_HISTORY_LIMIT } from "./history.ts";
4
4
  import { applyPatch, canonicalJson, containsNull, isJsonValue, isObject, type JsonObject } from "./json.ts";
5
5
  import { validateTemporalLineage, type TransitionBoundary } from "./temporal.ts";
6
- import { migrateLegacySkillCompilations } from "./skills.ts";
7
6
  import { isMaterializedState, type MaterializedState } from "./state.ts";
8
7
  const MAX_RESTORED_STEP = Number.MAX_SAFE_INTEGER - 1;
9
8
  const MAX_LEGACY_VALIDATION_ATTEMPT = 7;
@@ -15,11 +14,6 @@ export interface SnapshotConfig {
15
14
  enabled: boolean;
16
15
  }
17
16
 
18
- export interface PendingPublicationState {
19
- commit: string;
20
- error: string;
21
- }
22
-
23
17
  interface LegacyValidationFeedback {
24
18
  attempt: number;
25
19
  error: string;
@@ -27,14 +21,11 @@ interface LegacyValidationFeedback {
27
21
  }
28
22
 
29
23
  export interface SnapshotMeta {
30
- durableBase?: string;
31
- pendingPublication?: PendingPublicationState;
32
24
  step: number;
33
25
  specification?: string;
34
26
  /** Read-only compatibility/recovery diagnostic; 0.7 never schedules terminal-envelope retries. */
35
27
  validation?: LegacyValidationFeedback;
36
28
  bootstrap?: boolean;
37
- remotePublication?: RemotePublicationPolicyDocument;
38
29
  }
39
30
 
40
31
  /** In-memory runtime config/provenance; durable config/meta and scope files own restoration. */
@@ -47,17 +38,12 @@ export type Snapshot = StateFlowSnapshot;
47
38
 
48
39
  export interface SessionRuntime {
49
40
  config: SnapshotConfig;
50
- meta: Omit<SnapshotMeta, "durableBase" | "pendingPublication"> & {
41
+ meta: SnapshotMeta & {
51
42
  version: 1;
52
43
  identity: { cwd: string; sessionId: string };
53
44
  lineage: TransitionBoundary[];
54
- /** Runtime-owned artifact freshness evidence; never projected as semantic state. */
45
+ /** Runtime-owned artifact compilation evidence; never projected as semantic state. */
55
46
  artifacts?: ArtifactProvenanceRegistry;
56
- /** Resolved against the commit that last wrote this runtime record, not arbitrary HEAD. */
57
- revision: "self";
58
- temporalRevision?: "self" | string;
59
- /** Durable intent survives a crash before the push result can be observed. */
60
- publication: "unconfirmed" | "files";
61
47
  temporal?: { checkpoint: TransitionBoundary; patches: TransitionBoundary[] };
62
48
  [key: string]: unknown;
63
49
  };
@@ -66,27 +52,26 @@ export interface SessionRuntime {
66
52
  export function validateSessionRuntime(value: unknown, cwd: string, sessionId: string): asserts value is SessionRuntime {
67
53
  if (!isJsonValue(value) || !isObject(value) || Object.keys(value).sort().join(",") !== "config,meta"
68
54
  || !isObject(value.config) || !isObject(value.meta)) throw new Error("Invalid State Flow session runtime envelope");
69
- const { version, identity, lineage, revision, temporalRevision, publication, artifacts, temporal: _temporalScope, ...fields } = value.meta;
55
+ const { version, identity, lineage, artifacts, temporal: _temporalScope, ...fields } = value.meta;
70
56
  if (artifacts !== undefined) parseArtifactProvenanceRegistry(artifacts, "State Flow session artifact provenance");
71
- if (temporalRevision !== undefined && temporalRevision !== "self"
72
- && !isExactRevision(temporalRevision)) throw new Error("Invalid temporal revision reference");
73
- if (version !== 1 || revision !== "self" || (publication !== "unconfirmed" && publication !== "files")) throw new Error("Unsupported State Flow runtime provenance format");
74
- if (publication === "files" && temporalRevision !== undefined && temporalRevision !== "self") throw new Error("File runtime cannot select a historical temporal revision");
57
+ if (version !== 1) throw new Error("Unsupported State Flow runtime provenance format");
75
58
  if (!isObject(identity) || Object.keys(identity).sort().join(",") !== "cwd,sessionId"
76
59
  || identity.cwd !== resolve(cwd) || identity.sessionId !== sessionId || sessionId.trim().length === 0 || sessionId !== sessionId.trim()) {
77
60
  throw new Error("State Flow runtime scope identity mismatch");
78
61
  }
79
- validateTemporalLineage(lineage);
80
- const known = new Set(["step", "specification", "validation", "bootstrap", "remotePublication"]);
62
+ validateTemporalLineage(lineage, MAX_HISTORY_LIMIT);
63
+ const known = new Set(["step", "specification", "validation", "bootstrap"]);
81
64
  if (Object.keys(fields).some((key) => ["state", "contract", "working", "response"].includes(key))) {
82
65
  throw new Error("Semantic state does not belong in State Flow runtime metadata");
83
66
  }
84
67
  const runtimeFields = Object.fromEntries(Object.entries(fields).filter(([key]) => known.has(key)));
85
- const { transitionWindow: _retiredWindow, ...supportedConfig } = value.config;
86
- const normalized = migrateSnapshot({ config: supportedConfig, meta: runtimeFields });
68
+ const normalized: Snapshot = {
69
+ config: { enabled: value.config.enabled === true },
70
+ meta: restoredMeta(runtimeFields),
71
+ };
87
72
  if (runtimeFields.bootstrap === false) normalized.meta.bootstrap = false;
88
73
  if (runtimeFields.step === Number.MAX_SAFE_INTEGER) normalized.meta.step = Number.MAX_SAFE_INTEGER;
89
- if (canonicalJson({ config: normalized.config, meta: normalized.meta }) !== canonicalJson({ config: supportedConfig, meta: runtimeFields })) {
74
+ if (canonicalJson({ config: normalized.config, meta: normalized.meta }) !== canonicalJson({ config: value.config, meta: runtimeFields })) {
90
75
  throw new Error("Invalid State Flow runtime configuration or counters");
91
76
  }
92
77
  }
@@ -96,19 +81,16 @@ export function createSessionRuntime(
96
81
  cwd: string,
97
82
  sessionId: string,
98
83
  lineage: readonly TransitionBoundary[],
99
- publication: SessionRuntime["meta"]["publication"] = "unconfirmed",
100
84
  _artifacts: ArtifactProvenanceRegistry = {},
101
85
  ): SessionRuntime {
102
- const { durableBase: _base, pendingPublication: _publication, ...fields } = snapshot.meta;
86
+ const fields = snapshot.meta;
103
87
  const runtime: SessionRuntime = {
104
88
  config: structuredClone(snapshot.config),
105
89
  meta: {
106
- ...Object.fromEntries(Object.entries(fields).filter(([, value]) => value !== undefined)) as Omit<SnapshotMeta, "durableBase" | "pendingPublication">,
90
+ ...Object.fromEntries(Object.entries(fields).filter(([, value]) => value !== undefined)) as SnapshotMeta,
107
91
  version: 1,
108
92
  identity: { cwd: resolve(cwd), sessionId },
109
93
  lineage: structuredClone([...lineage]),
110
- revision: "self",
111
- publication,
112
94
  },
113
95
  };
114
96
  validateSessionRuntime(runtime, cwd, sessionId);
@@ -124,24 +106,13 @@ export function serializeSessionRuntime(
124
106
  }
125
107
 
126
108
  export function parseSessionRuntime(
127
- config: string | undefined, runtimeSource: string | undefined, cwd: string, sessionId: string, legacyMetaSource?: string,
109
+ config: string | undefined, runtimeSource: string | undefined, cwd: string, sessionId: string,
128
110
  ): SessionRuntime | undefined {
129
- let selected = runtimeSource;
130
- if (selected === undefined && legacyMetaSource !== undefined) {
131
- let legacy: unknown;
132
- try { legacy = JSON.parse(legacyMetaSource); } catch { throw new Error("State Flow session runtime contains invalid JSON"); }
133
- if (isObject(legacy) && (Object.hasOwn(legacy, "identity") || Object.hasOwn(legacy, "lineage"))) selected = legacyMetaSource;
134
- }
135
- if (config === undefined && selected === undefined) return undefined;
136
- if (config === undefined && selected !== undefined) {
137
- let document: unknown;
138
- try { document = JSON.parse(selected); } catch { throw new Error("State Flow session runtime contains invalid JSON"); }
139
- if (isObject(document) && !Object.hasOwn(document, "identity") && !Object.hasOwn(document, "lineage")) return undefined;
140
- }
141
- if (config === undefined || selected === undefined) throw new Error("Incomplete State Flow config/runtime pair");
111
+ if (config === undefined && runtimeSource === undefined) return undefined;
112
+ if (config === undefined || runtimeSource === undefined) throw new Error("Incomplete State Flow config/runtime pair");
142
113
  let runtime: unknown;
143
114
  try {
144
- runtime = { config: JSON.parse(config), meta: JSON.parse(selected) };
115
+ runtime = { config: JSON.parse(config), meta: JSON.parse(runtimeSource) };
145
116
  } catch {
146
117
  throw new Error("State Flow session runtime contains invalid JSON");
147
118
  }
@@ -150,40 +121,6 @@ export function parseSessionRuntime(
150
121
  return { config: { enabled: runtime.config.enabled }, meta: runtimeMeta };
151
122
  }
152
123
 
153
- export function resolveSessionRuntime(runtime: SessionRuntime, revision: string): { snapshot: Snapshot; lineage: TransitionBoundary[]; publicationTarget: string; artifacts: ArtifactProvenanceRegistry } {
154
- validateSessionRuntime(runtime, runtime.meta.identity.cwd, runtime.meta.identity.sessionId);
155
- if (!isExactRevision(revision) || runtime.meta.publication !== "unconfirmed") throw new Error("Runtime self reference requires its exact Git revision and Git publication provenance");
156
- const { version: _version, identity: _identity, lineage, revision: _self, temporalRevision: _temporal, publication: _intent, artifacts, ...fields } = runtime.meta;
157
- return {
158
- snapshot: { config: structuredClone(runtime.config), meta: { ...structuredClone(fields), durableBase: revision } },
159
- lineage: structuredClone(lineage),
160
- publicationTarget: revision,
161
- artifacts: structuredClone(artifacts ?? {}),
162
- };
163
- }
164
-
165
- export function resolveFileSessionRuntime(runtime: SessionRuntime, revision: string): Snapshot {
166
- validateSessionRuntime(runtime, runtime.meta.identity.cwd, runtime.meta.identity.sessionId);
167
- if (!isFileRevision(revision) || runtime.meta.publication !== "files") throw new Error("File runtime requires its exact file revision and file publication provenance");
168
- const { version: _version, identity: _identity, lineage: _lineage, revision: _self, temporalRevision: _temporal, publication: _intent, ...fields } = runtime.meta;
169
- return { config: structuredClone(runtime.config), meta: { ...structuredClone(fields), durableBase: revision } };
170
- }
171
-
172
- function isLegacyTwoPartState(value: unknown): value is { contract: JsonObject; working: JsonObject } {
173
- return isObject(value)
174
- && isObject(value.contract)
175
- && isObject(value.working)
176
- && Object.keys(value).every((key) => key === "contract" || key === "working");
177
- }
178
-
179
- function isLegacyThreePartState(value: unknown): value is { contract: JsonObject; working: JsonObject; response: string } {
180
- return isObject(value)
181
- && isObject(value.contract)
182
- && isObject(value.working)
183
- && typeof value.response === "string"
184
- && Object.keys(value).every((key) => key === "contract" || key === "working" || key === "response");
185
- }
186
-
187
124
  function restoredStep(value: unknown): number {
188
125
  return typeof value === "number"
189
126
  && Number.isSafeInteger(value)
@@ -207,37 +144,14 @@ function restoredValidation(value: unknown): LegacyValidationFeedback | undefine
207
144
  };
208
145
  }
209
146
 
210
- function restoredPendingPublication(value: unknown): PendingPublicationState | undefined {
211
- if (!isObject(value)
212
- || typeof value.commit !== "string"
213
- || !/^[0-9a-f]{40,64}$/.test(value.commit)
214
- || typeof value.error !== "string"
215
- || value.error.trim().length === 0) return undefined;
216
- return { commit: value.commit, error: value.error };
217
- }
218
-
219
147
  function restoredMeta(value: unknown, legacy: JsonObject = {}): SnapshotMeta {
220
148
  const meta = isObject(value) ? value : legacy;
221
- const pendingPublication = restoredPendingPublication(meta.pendingPublication);
222
149
  const validation = restoredValidation(meta.validation);
223
- let remotePublication: RemotePublicationPolicyDocument | undefined;
224
- try {
225
- if (meta.remotePublication !== undefined) remotePublication = serializeRemotePublicationPolicyDocument(
226
- parseRemotePublicationPolicyDocument(meta.remotePublication, { legacyRuntime: false }),
227
- );
228
- } catch {
229
- remotePublication = undefined;
230
- }
231
150
  return {
232
- ...(isDurableRevision(meta.durableBase)
233
- ? { durableBase: meta.durableBase }
234
- : {}),
235
- ...(pendingPublication === undefined ? {} : { pendingPublication }),
236
151
  step: restoredStep(meta.step),
237
152
  ...(typeof meta.specification === "string" ? { specification: meta.specification } : {}),
238
153
  ...(validation === undefined ? {} : { validation }),
239
154
  ...(meta.bootstrap === true ? { bootstrap: true } : {}),
240
- ...(remotePublication === undefined ? {} : { remotePublication }),
241
155
  };
242
156
  }
243
157
 
@@ -249,41 +163,53 @@ export function emptySnapshot(enabled = false): Snapshot {
249
163
  return envelope(enabled, { step: 0 });
250
164
  }
251
165
 
252
- export type PiCheckpoint = { revision: string } | { disabled: true };
166
+ export type RetainedBoundaryCheckpoint = {
167
+ boundary: string;
168
+ enabled: boolean;
169
+ step: number;
170
+ bootstrap?: true;
171
+ specification?: string;
172
+ };
173
+ export type RetainedPiCheckpoint = RetainedBoundaryCheckpoint | { disabled: true };
253
174
  export type FileRevision = `file:${string}`;
254
175
 
255
176
  export function isFileRevision(value: unknown): value is FileRevision {
256
177
  return typeof value === "string" && /^file:[0-9a-f]{64}$/.test(value);
257
178
  }
258
179
 
259
- export function isDurableRevision(value: unknown): value is string {
260
- return isExactRevision(value) || isFileRevision(value);
261
- }
262
-
263
- export function isExactRevision(value: unknown): value is string {
264
- return typeof value === "string" && /^(?:[0-9a-f]{40}|[0-9a-f]{64})$/.test(value);
265
- }
266
-
267
- export function persistableSnapshot(snapshot: Snapshot): PiCheckpoint {
268
- if (snapshot.meta.durableBase !== undefined) {
269
- if (!isDurableRevision(snapshot.meta.durableBase)) throw new Error("Checkpoint requires an exact Git revision or file reference");
270
- return { revision: snapshot.meta.durableBase };
271
- }
272
- if (snapshot.config.enabled) throw new Error("Enabled checkpoint requires a durable runtime revision");
273
- return { disabled: true };
274
- }
275
-
276
- /** Pi checkpoints admit only strict revision pointers or the disabled marker. */
277
- export function parsePiCheckpoint(value: unknown): PiCheckpoint {
278
- if (!isObject(value)) throw new Error("Invalid State Flow checkpoint");
279
- if (Object.hasOwn(value, "revision") || Object.hasOwn(value, "disabled")) {
280
- if (Object.keys(value).length === 1) {
281
- if (isDurableRevision(value.revision)) return { revision: value.revision };
282
- if (value.disabled === true) return { disabled: true };
283
- }
284
- throw new Error("Invalid State Flow checkpoint pointer or disabled marker");
180
+ /** Encode branch lifecycle against one retained temporal identity without semantic or backup data. */
181
+ export function retainedBoundaryCheckpoint(snapshot: Snapshot, boundary: string): RetainedBoundaryCheckpoint {
182
+ if (typeof boundary !== "string" || boundary.trim().length === 0) throw new Error("Checkpoint requires a retained temporal boundary identity");
183
+ const checkpoint: RetainedBoundaryCheckpoint = {
184
+ boundary,
185
+ enabled: snapshot.config.enabled,
186
+ step: snapshot.meta.step,
187
+ ...(snapshot.meta.bootstrap === true ? { bootstrap: true as const } : {}),
188
+ ...(snapshot.meta.specification === undefined ? {} : { specification: snapshot.meta.specification }),
189
+ };
190
+ return parseRetainedPiCheckpoint(checkpoint) as RetainedBoundaryCheckpoint;
191
+ }
192
+
193
+ /** Decode the 0.17 retained-window checkpoint contract. */
194
+ export function parseRetainedPiCheckpoint(value: unknown): RetainedPiCheckpoint {
195
+ if (!isObject(value)) throw new Error("Invalid State Flow retained-boundary checkpoint");
196
+ if (Object.keys(value).length === 1 && value.disabled === true) return { disabled: true };
197
+ const allowed = new Set(["boundary", "enabled", "step", "bootstrap", "specification"]);
198
+ if (Object.keys(value).some((key) => !allowed.has(key))
199
+ || typeof value.boundary !== "string" || value.boundary.trim().length === 0
200
+ || typeof value.enabled !== "boolean"
201
+ || !Number.isSafeInteger(value.step) || (value.step as number) < 0 || (value.step as number) > MAX_RESTORED_STEP
202
+ || (value.bootstrap !== undefined && value.bootstrap !== true)
203
+ || (value.specification !== undefined && typeof value.specification !== "string")) {
204
+ throw new Error("Invalid State Flow retained-boundary checkpoint");
285
205
  }
286
- throw new Error("Unrecognized State Flow checkpoint");
206
+ return {
207
+ boundary: value.boundary,
208
+ enabled: value.enabled,
209
+ step: value.step as number,
210
+ ...(value.bootstrap === true ? { bootstrap: true } : {}),
211
+ ...(typeof value.specification === "string" ? { specification: value.specification } : {}),
212
+ };
287
213
  }
288
214
 
289
215
  export function migrationFailure(data: JsonObject, error: string): Snapshot {
@@ -295,13 +221,3 @@ export function migrationFailure(data: JsonObject, error: string): Snapshot {
295
221
  };
296
222
  return envelope(false, meta);
297
223
  }
298
-
299
- export function migrateSnapshot(value: unknown): Snapshot {
300
- if (!isObject(value)) return emptySnapshot();
301
- const isEnvelope = Object.hasOwn(value, "config") || Object.hasOwn(value, "meta");
302
- const config = isEnvelope && isObject(value.config) ? value.config : value;
303
- const meta = restoredMeta(isEnvelope ? value.meta : undefined, value);
304
- const enabled = config.enabled === true;
305
- if (Object.hasOwn(value, "state") || Object.hasOwn(value, "stateBasis") || Object.hasOwn(value, "previousStatePatch")) return envelope(false, meta);
306
- return envelope(enabled, meta);
307
- }
@@ -3,19 +3,19 @@ import {
3
3
  projectArtifactsForModel,
4
4
  updateArtifactRegistry,
5
5
  type ArtifactCompilationUpdate,
6
+ type ArtifactModelHints,
6
7
  type ArtifactRegistry,
7
8
  } from "./artifact.ts";
8
- import { applyPatch, isJsonValue, isObject, type JsonObject, type JsonValue } from "./json.ts";
9
+ import { applyPatch, isObject, type JsonObject } from "./json.ts";
9
10
 
10
11
  /** The canonical semantic state shape shared by global, CWD, and session scopes. */
11
12
  export type MaterializedState = JsonObject & {
12
- artifacts: ArtifactRegistry;
13
+ intents: JsonObject;
13
14
  contract: JsonObject;
14
15
  working: JsonObject;
15
- intents: JsonObject;
16
+ artifacts: ArtifactRegistry;
16
17
  response: string;
17
- /** Absent is the canonical empty lazy plane and preserves predecessor-store compatibility. */
18
- lazy?: JsonValue;
18
+ lazy: JsonObject;
19
19
  };
20
20
 
21
21
  /** Compatibility name for callers that still treat materialized state as a document. */
@@ -28,6 +28,7 @@ export interface StatePatch extends JsonObject {
28
28
  working: JsonObject;
29
29
  intents: JsonObject;
30
30
  response: string;
31
+ lazy: JsonObject;
31
32
  }
32
33
 
33
34
  export type StateScope = "global" | "cwd" | "session";
@@ -38,7 +39,7 @@ export interface ScopePatch {
38
39
  contract?: JsonObject;
39
40
  working?: JsonObject;
40
41
  intents?: JsonObject;
41
- lazy?: JsonValue;
42
+ lazy?: JsonObject;
42
43
  }
43
44
 
44
45
  export interface ScopedPatch {
@@ -68,7 +69,7 @@ export interface ScopedStates {
68
69
  }
69
70
 
70
71
  export function emptyState(): MaterializedState {
71
- return { artifacts: {}, contract: {}, working: {}, intents: {}, response: "" } as MaterializedState;
72
+ return { intents: {}, contract: {}, working: {}, artifacts: {}, response: "", lazy: {} };
72
73
  }
73
74
 
74
75
  export function isMaterializedState(value: unknown): value is MaterializedState {
@@ -78,19 +79,12 @@ export function isMaterializedState(value: unknown): value is MaterializedState
78
79
  && isObject(value.working)
79
80
  && isObject(value.intents)
80
81
  && typeof value.response === "string"
81
- && (!Object.hasOwn(value, "lazy") || (isJsonValue(value.lazy) && value.lazy !== null))
82
+ && isObject(value.lazy)
82
83
  && Object.keys(value).every((key) => key === "artifacts" || key === "contract" || key === "working" || key === "intents" || key === "response" || key === "lazy");
83
84
  }
84
85
 
85
86
  export const isStateDocument = isMaterializedState;
86
87
 
87
- /** Upgrade one exact pre-intents materialized state without inferring commitments. */
88
- export function migratePreIntentState(value: unknown): MaterializedState | undefined {
89
- if (!isObject(value) || Object.hasOwn(value, "intents")) return undefined;
90
- const candidate = { ...structuredClone(value), intents: {} };
91
- return isMaterializedState(candidate) ? candidate : undefined;
92
- }
93
-
94
88
  /** Atomically replace compiled and removed artifacts inside one materialized scope. */
95
89
  export function updateMaterializedArtifacts(
96
90
  state: MaterializedState,
@@ -109,8 +103,22 @@ export function overlayStates(...scopes: readonly MaterializedState[]): Material
109
103
  }, emptyState());
110
104
  }
111
105
 
112
- /** Model-visible projection: runtime artifact bookkeeping never reaches ordinary context. */
113
- export function projectStateForModel(state: MaterializedState): MaterializedState {
114
- const { lazy: _lazy, ...hot } = structuredClone(state);
115
- return { ...hot, artifacts: projectArtifactsForModel(state.artifacts) } as MaterializedState;
106
+ export type ModelState = JsonObject & {
107
+ intents: JsonObject;
108
+ contract: JsonObject;
109
+ working: JsonObject;
110
+ artifacts: ArtifactRegistry;
111
+ response: string;
112
+ };
113
+
114
+ /** Model-visible projection: lazy bodies and runtime artifact bookkeeping stay out of ordinary context. */
115
+ export function projectStateForModel(state: MaterializedState, artifactHints: ArtifactModelHints = {}): ModelState {
116
+ const cloned = structuredClone(state);
117
+ return {
118
+ intents: cloned.intents,
119
+ contract: cloned.contract,
120
+ working: cloned.working,
121
+ artifacts: projectArtifactsForModel(cloned.artifacts, artifactHints),
122
+ response: cloned.response,
123
+ };
116
124
  }
@@ -1,7 +1,6 @@
1
1
  import type { ArtifactInvalidationReason } from "./artifact.ts";
2
- import { projectRecentTransitionsWithLimit, RECENT_TRANSITION_LIMIT, type RecentTransitionWindow } from "./history.ts";
3
- import { inspectMemoryPromotions, retainedMemoryScopes } from "./memory.ts";
4
- import type { PublicationQueueState } from "./publication.ts";
2
+ import { projectRecentTransitionsWithLimit, type RecentTransitionWindow } from "./history.ts";
3
+ import { retainedMemoryScopes } from "./memory.ts";
5
4
  import type { Snapshot } from "./snapshot.ts";
6
5
  import { overlayStates, type ScopedStates, type StateScope } from "./state.ts";
7
6
  import type { TransitionBoundary } from "./temporal.ts";
@@ -18,24 +17,16 @@ export interface StaleArtifactDiagnostic {
18
17
  reason: StaleArtifactReason;
19
18
  }
20
19
 
21
- export interface PendingPublicationDiagnostic {
22
- commit: string;
23
- error: string;
24
- }
25
-
26
20
  export interface StatusDiagnostics {
27
21
  repositoryRoot: string;
28
22
  cwdScopeKey: string;
29
23
  sessionScopeKey: string;
30
24
  scopeStates: ScopedStates;
31
25
  recent: RecentTransitionWindow;
26
+ historyLimit: number;
32
27
  temporal?: { head: TransitionBoundary; historyDepth: number; tailCounts: Record<StateScope, number> };
33
28
  staleArtifacts: readonly StaleArtifactDiagnostic[];
34
- artifactFreshnessError?: string;
35
29
  durableStateError?: string;
36
- pendingPublication?: PendingPublicationDiagnostic;
37
- publicationQueue?: PublicationQueueState;
38
- publicationQueueError?: string;
39
30
  }
40
31
 
41
32
  export function compactStatus(snapshot: Snapshot, colorize: Colorize): string | undefined {
@@ -47,13 +38,9 @@ function countArtifacts(states: ScopedStates, scope: StateScope): number {
47
38
  return Object.keys(states[scope].artifacts).length;
48
39
  }
49
40
 
50
- function abbreviatedCommit(commit: string): string {
51
- return commit.length > 12 ? commit.slice(0, 12) : commit;
52
- }
53
-
54
41
  export function detailedStatus(snapshot: Snapshot, diagnostics: StatusDiagnostics): string {
55
42
  const projectedRecent = projectRecentTransitionsWithLimit(
56
- RECENT_TRANSITION_LIMIT,
43
+ diagnostics.historyLimit,
57
44
  diagnostics.recent,
58
45
  );
59
46
  const available = diagnostics.temporal !== undefined && diagnostics.durableStateError === undefined;
@@ -63,61 +50,36 @@ export function detailedStatus(snapshot: Snapshot, diagnostics: StatusDiagnostic
63
50
  diagnostics.scopeStates.session,
64
51
  );
65
52
  const stateJson = materialized === undefined ? undefined : JSON.stringify(materialized, null, 2);
66
- const freshnessError = diagnostics.artifactFreshnessError ?? (available ? undefined : "temporal global artifact registry is unavailable");
67
- const stale = freshnessError === undefined
68
- ? String(diagnostics.staleArtifacts.length)
69
- : "unknown";
70
- const publication = diagnostics.pendingPublication === undefined
71
- ? "idle"
72
- : `pending ${abbreviatedCommit(diagnostics.pendingPublication.commit)} — ${diagnostics.pendingPublication.error}`;
73
- const staleLines = freshnessError !== undefined
74
- ? [`Artifact freshness unavailable: ${freshnessError}`]
75
- : diagnostics.staleArtifacts.length === 0
76
- ? ["Stale artifacts: none"]
77
- : [
78
- "Stale artifacts:",
79
- ...diagnostics.staleArtifacts.map(({ scope, path, reason }) => `- [${scope}] ${path} — ${reason}`),
80
- ];
53
+ const invalidated = available ? String(diagnostics.staleArtifacts.length) : "unavailable";
54
+ const invalidationLines = diagnostics.staleArtifacts.length === 0
55
+ ? ["Pending artifact invalidations: none"]
56
+ : [
57
+ "Pending artifact invalidations:",
58
+ ...diagnostics.staleArtifacts.map(({ scope, path, reason }) => `- [${scope}] ${path} — ${reason}`),
59
+ ];
81
60
  const temporal = available ? diagnostics.temporal : undefined;
82
61
  const temporalLines = temporal === undefined
83
62
  ? [`Temporal materialization unavailable: ${diagnostics.durableStateError ?? "no selected branch runtime"}`,
84
- "Hot history: unavailable; offsets beyond 7 require explicit cold Git inspection",
63
+ `Hot history: unavailable; configured maximum depth ${diagnostics.historyLimit}`,
85
64
  "Retained patch tails: unavailable"]
86
65
  : [`Temporal head: ${JSON.stringify(temporal.head.id)}; branch-local position ${temporal.head.position}`,
87
- `Hot history: offsets 0..${temporal.historyDepth}; maximum depth 7`,
66
+ `Hot history: offsets 0..${temporal.historyDepth}; maximum depth ${diagnostics.historyLimit}`,
88
67
  `Retained patch tails: global ${temporal.tailCounts.global}; CWD ${temporal.tailCounts.cwd}; session ${temporal.tailCounts.session}`];
89
68
  const artifacts = (scope: StateScope) => available ? countArtifacts(diagnostics.scopeStates, scope) : "unknown";
90
- const promotions = available ? inspectMemoryPromotions(diagnostics.scopeStates.global) : [];
91
- const promotionCounts = Object.fromEntries(["pending", "accepted", "failed", "unknown", "invalid"].map((status) => [status, promotions.filter((entry) => entry.status === status).length]));
92
69
  const memoryScopes = available ? retainedMemoryScopes(diagnostics.scopeStates) : undefined;
93
- const oneLine = (value: string) => value.replace(/\s+/g, " ").slice(0, 240);
94
- const promotionLines = !available || promotions.length === 0 ? [] : [
95
- "Memory promotions:",
96
- ...promotions.map((entry) => `- ${oneLine(entry.id)} — ${entry.status}; owner ${oneLine(entry.owner ?? "unavailable")}${entry.pointer ? `; pointer ${oneLine(entry.pointer)}` : ""}${entry.revision ? `; revision ${oneLine(entry.revision)}` : ""}${entry.error ? `; error ${oneLine(entry.error)}` : ""}`),
97
- ];
98
70
 
99
71
  return [
100
72
  `State Flow diagnostics — config.enabled=${snapshot.config.enabled}; branch mode=${snapshot.config.enabled ? "active" : "inactive"}`,
101
73
  `Repository: ${diagnostics.repositoryRoot}`,
102
74
  `Scope keys: CWD ${diagnostics.cwdScopeKey}; session ${diagnostics.sessionScopeKey}`,
103
75
  "Session files: config.json owns behavior; runtime.json owns branch recovery; meta.json owns scope provenance",
104
- `Runtime metadata: step #${snapshot.meta.step}; active revision ${snapshot.meta.durableBase ?? "none"}; bootstrap ${snapshot.meta.bootstrap === true}`,
105
- `Remote publication policy: ${snapshot.meta.remotePublication?.mode ?? "legacy-transition"}`,
106
- diagnostics.publicationQueueError !== undefined
107
- ? `Remote queue: unavailable; error ${diagnostics.publicationQueueError}`
108
- : diagnostics.publicationQueue === undefined
109
- ? "Remote queue: idle"
110
- : `Remote queue: ${diagnostics.publicationQueue.status}; target ${abbreviatedCommit(diagnostics.publicationQueue.target)}; confirmed ${diagnostics.publicationQueue.confirmed ? abbreviatedCommit(diagnostics.publicationQueue.confirmed) : "none"}; attempt ${diagnostics.publicationQueue.attempt}${diagnostics.publicationQueue.error ? `; error ${diagnostics.publicationQueue.error}` : ""}`,
76
+ `Runtime metadata: step #${snapshot.meta.step}; bootstrap ${snapshot.meta.bootstrap === true}`,
111
77
  "Memory: owner state-flow; global retention enabled; global fallback active",
112
78
  `Memory-bearing scopes: global ${memoryScopes?.global ?? "unknown"}; CWD ${memoryScopes?.cwd ?? "unknown"}; session ${memoryScopes?.session ?? "unknown"}`,
113
- `Promotion status: pending ${promotionCounts.pending}; accepted ${promotionCounts.accepted}; failed ${promotionCounts.failed}; unknown ${promotionCounts.unknown}; invalid ${promotionCounts.invalid}`,
114
- ...promotionLines,
115
79
  ...temporalLines,
116
- `Artifacts: global ${artifacts("global")}; CWD ${artifacts("cwd")}; session ${artifacts("session")}; stale ${stale}`,
80
+ `Artifacts: global ${artifacts("global")}; CWD ${artifacts("cwd")}; session ${artifacts("session")}; pending invalidations ${invalidated}`,
117
81
  available ? `Recent transitions: global ${diagnostics.recent.filter(({ transitions }) => transitions.some(({ scope }) => scope === "global")).length}; CWD ${diagnostics.recent.filter(({ transitions }) => transitions.some(({ scope }) => scope === "cwd")).length}; session ${diagnostics.recent.filter(({ transitions }) => transitions.some(({ scope }) => scope === "session")).length}; active ${projectedRecent.length}` : "Recent transitions: unavailable",
118
- `Publication policy: ${snapshot.meta.remotePublication?.mode ?? "legacy-unresolved"}`,
119
- `Publication: ${publication}`,
120
- ...staleLines,
82
+ ...invalidationLines,
121
83
  ...(stateJson === undefined
122
84
  ? ["Effective memory: unavailable"]
123
85
  : [`Effective memory (${Buffer.byteLength(stateJson, "utf8")} JSON bytes; global → CWD → session overlay):`, "", stateJson]),