@codyswann/lisa 4.74.7 → 4.74.9

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 (66) hide show
  1. package/all/copy-overwrite/scripts/lib/npm-update-cancel-origin.mjs +4 -2
  2. package/all/copy-overwrite/scripts/lib/npm-update-checkpoint-codec.mjs +141 -0
  3. package/all/copy-overwrite/scripts/lib/npm-update-checkpoint.mjs +162 -116
  4. package/all/copy-overwrite/scripts/lib/npm-update-helper-inventory.mjs +1 -0
  5. package/all/copy-overwrite/scripts/lib/npm-update-recovery.mjs +9 -60
  6. package/all/copy-overwrite/scripts/npm-updater-helper-graph.json +18 -6
  7. package/dist/core/lisa-owned-hash-ledger.d.ts.map +1 -1
  8. package/dist/core/lisa-owned-hash-ledger.js +20 -0
  9. package/dist/core/lisa-owned-hash-ledger.js.map +1 -1
  10. package/dist/core/nightly-e2e-guard-behavior-certificate.js +2 -2
  11. package/dist/core/upstream-evidence-manifest.d.ts.map +1 -1
  12. package/dist/core/upstream-evidence-manifest.js +12 -7
  13. package/dist/core/upstream-evidence-manifest.js.map +1 -1
  14. package/package.json +4 -4
  15. package/plugins/lisa/.claude-plugin/plugin.json +1 -1
  16. package/plugins/lisa/.codex-plugin/plugin.json +1 -1
  17. package/plugins/lisa-agy/plugin.json +1 -1
  18. package/plugins/lisa-cdk/.claude-plugin/plugin.json +1 -1
  19. package/plugins/lisa-cdk/.codex-plugin/plugin.json +1 -1
  20. package/plugins/lisa-cdk-agy/plugin.json +1 -1
  21. package/plugins/lisa-cdk-copilot/.claude-plugin/plugin.json +1 -1
  22. package/plugins/lisa-cdk-cursor/.claude-plugin/plugin.json +1 -1
  23. package/plugins/lisa-copilot/.claude-plugin/plugin.json +1 -1
  24. package/plugins/lisa-cursor/.claude-plugin/plugin.json +1 -1
  25. package/plugins/lisa-expo/.claude-plugin/plugin.json +1 -1
  26. package/plugins/lisa-expo/.codex-plugin/plugin.json +1 -1
  27. package/plugins/lisa-expo-agy/plugin.json +1 -1
  28. package/plugins/lisa-expo-copilot/.claude-plugin/plugin.json +1 -1
  29. package/plugins/lisa-expo-cursor/.claude-plugin/plugin.json +1 -1
  30. package/plugins/lisa-harper-fabric/.claude-plugin/plugin.json +1 -1
  31. package/plugins/lisa-harper-fabric/.codex-plugin/plugin.json +1 -1
  32. package/plugins/lisa-harper-fabric-agy/plugin.json +1 -1
  33. package/plugins/lisa-harper-fabric-copilot/.claude-plugin/plugin.json +1 -1
  34. package/plugins/lisa-harper-fabric-cursor/.claude-plugin/plugin.json +1 -1
  35. package/plugins/lisa-nestjs/.claude-plugin/plugin.json +1 -1
  36. package/plugins/lisa-nestjs/.codex-plugin/plugin.json +1 -1
  37. package/plugins/lisa-nestjs-agy/plugin.json +1 -1
  38. package/plugins/lisa-nestjs-copilot/.claude-plugin/plugin.json +1 -1
  39. package/plugins/lisa-nestjs-cursor/.claude-plugin/plugin.json +1 -1
  40. package/plugins/lisa-openclaw/.claude-plugin/plugin.json +1 -1
  41. package/plugins/lisa-openclaw/.codex-plugin/plugin.json +1 -1
  42. package/plugins/lisa-openclaw-agy/plugin.json +1 -1
  43. package/plugins/lisa-openclaw-copilot/.claude-plugin/plugin.json +1 -1
  44. package/plugins/lisa-openclaw-cursor/.claude-plugin/plugin.json +1 -1
  45. package/plugins/lisa-phaser/.claude-plugin/plugin.json +1 -1
  46. package/plugins/lisa-phaser/.codex-plugin/plugin.json +1 -1
  47. package/plugins/lisa-phaser-agy/plugin.json +1 -1
  48. package/plugins/lisa-phaser-copilot/.claude-plugin/plugin.json +1 -1
  49. package/plugins/lisa-phaser-cursor/.claude-plugin/plugin.json +1 -1
  50. package/plugins/lisa-rails/.claude-plugin/plugin.json +1 -1
  51. package/plugins/lisa-rails/.codex-plugin/plugin.json +1 -1
  52. package/plugins/lisa-rails-agy/plugin.json +1 -1
  53. package/plugins/lisa-rails-copilot/.claude-plugin/plugin.json +1 -1
  54. package/plugins/lisa-rails-cursor/.claude-plugin/plugin.json +1 -1
  55. package/plugins/lisa-typescript/.claude-plugin/plugin.json +1 -1
  56. package/plugins/lisa-typescript/.codex-plugin/plugin.json +1 -1
  57. package/plugins/lisa-typescript-agy/plugin.json +1 -1
  58. package/plugins/lisa-typescript-copilot/.claude-plugin/plugin.json +1 -1
  59. package/plugins/lisa-typescript-cursor/.claude-plugin/plugin.json +1 -1
  60. package/plugins/lisa-wiki/.claude-plugin/plugin.json +1 -1
  61. package/plugins/lisa-wiki/.codex-plugin/plugin.json +1 -1
  62. package/plugins/lisa-wiki-agy/plugin.json +1 -1
  63. package/plugins/lisa-wiki-copilot/.claude-plugin/plugin.json +1 -1
  64. package/plugins/lisa-wiki-cursor/.claude-plugin/plugin.json +1 -1
  65. package/rails/copy-overwrite/lefthook.yml +2 -2
  66. package/scripts/two-channel-couplings.json +1 -1
@@ -45,7 +45,7 @@ export function completeCheckpoints(comments) {
45
45
  );
46
46
  const digests = new Set();
47
47
  for (const comment of comments) {
48
- const match = /^\[lisa-npm-checkpoint\] v1 ([a-f0-9]{64}) complete\n/.exec(
48
+ const match = /^\[lisa-npm-checkpoint\] \S+ ([a-f0-9]{64}) complete\n/.exec(
49
49
  comment.body ?? ""
50
50
  );
51
51
  if (match) digests.add(match[1]);
@@ -113,7 +113,9 @@ export function assertOriginTransport(
113
113
  required(chronology, "invalid original transport chronology");
114
114
  const { start, end } = chronology;
115
115
  const selected = comments.filter(comment =>
116
- comment.body?.startsWith(`[lisa-npm-checkpoint] v1 ${digest} `)
116
+ new RegExp(`^\\[lisa-npm-checkpoint\\] v[12] ${digest} `).test(
117
+ comment.body ?? ""
118
+ )
117
119
  );
118
120
  required(selected.length > 0, "original transport is missing");
119
121
  for (const comment of selected) {
@@ -0,0 +1,141 @@
1
+ // This file is managed by Lisa and IS replaced on each `lisa` run.
2
+ // Do not edit directly — durable changes belong upstream in Lisa.
3
+
4
+ /**
5
+ * @file npm-update-checkpoint-codec.mjs
6
+ * @description Reduce serial public writes without extending signed authorization freshness.
7
+ * @module npm-updater
8
+ */
9
+ import { TextDecoder } from "node:util";
10
+ import { deflateSync, inflateSync } from "node:zlib";
11
+ import { canonicalJson } from "../lisa-automation-provenance.mjs";
12
+ import { keys, required } from "./npm-update-contract.mjs";
13
+ import { sha256 } from "./github-attestation-verifier.mjs";
14
+
15
+ const HEX = /^[a-f0-9]{64}$/;
16
+
17
+ export const PAYLOAD_MAXIMUM = 4_194_304;
18
+ export const TRANSPORT_MAXIMUM = 4_194_304;
19
+
20
+ /** Closed public payloads contain no bearer token or executable extension field. */
21
+ export function checkpointPayloadBytes(payload) {
22
+ keys(payload, ["version", "proposal", "allocation", "preview", "bundle"]);
23
+ required(
24
+ payload.version === 1 && typeof payload.bundle === "string",
25
+ "invalid public checkpoint payload"
26
+ );
27
+ const bytes = Buffer.from(`${canonicalJson(payload)}\n`);
28
+ required(bytes.length <= PAYLOAD_MAXIMUM, "checkpoint payload exceeds bound");
29
+ required(
30
+ !/\beyJ[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\b/.test(
31
+ payload.bundle
32
+ ),
33
+ "bearer JWT cannot be retained"
34
+ );
35
+ return bytes;
36
+ }
37
+
38
+ /** The fixed codec changes transport only; signed subjects and canonical payloads stay intact. */
39
+ export function encodeCheckpointBytes(bytes, version) {
40
+ required(
41
+ version === 1 || version === 2,
42
+ "unknown checkpoint transport version"
43
+ );
44
+ const encoded = version === 1 ? bytes : deflateSync(bytes, { level: 6 });
45
+ required(
46
+ encoded.length <= TRANSPORT_MAXIMUM,
47
+ "checkpoint transport exceeds bound"
48
+ );
49
+ return encoded;
50
+ }
51
+
52
+ /** An independent output ceiling and exact stream consumption refuse bombs and appended streams. */
53
+ export function decodeCheckpointBytes(transport, version) {
54
+ required(
55
+ (version === 1 || version === 2) &&
56
+ transport.length > 0 &&
57
+ transport.length <= TRANSPORT_MAXIMUM,
58
+ "checkpoint transport bounds or version differ"
59
+ );
60
+ const inflated =
61
+ version === 2
62
+ ? inflateSync(transport, { info: true, maxOutputLength: PAYLOAD_MAXIMUM })
63
+ : undefined;
64
+ required(
65
+ !inflated || inflated.engine.bytesWritten === transport.length,
66
+ "checkpoint compressed stream has trailing bytes"
67
+ );
68
+ const bytes = inflated ? inflated.buffer : transport;
69
+ required(
70
+ bytes.length <= PAYLOAD_MAXIMUM,
71
+ "checkpoint decoded payload exceeds bound"
72
+ );
73
+ const payload = JSON.parse(
74
+ new TextDecoder("utf-8", { fatal: true, ignoreBOM: true }).decode(bytes)
75
+ );
76
+ required(
77
+ checkpointPayloadBytes(payload).equals(bytes),
78
+ "checkpoint is noncanonical or duplicate-keyed"
79
+ );
80
+ return { payload, bytes };
81
+ }
82
+
83
+ /** Bind decoded bytes independently from the compressed stream and each numbered comment. */
84
+ export function manifestFor(bytes, transport, chunks, digest, version) {
85
+ return {
86
+ version,
87
+ ...(version === 2
88
+ ? {
89
+ encoding: "deflate",
90
+ transportSha256: sha256(transport),
91
+ transportBytes: transport.length,
92
+ }
93
+ : {}),
94
+ originDescriptorSha256: digest,
95
+ payloadSha256: sha256(bytes),
96
+ bytes: bytes.length,
97
+ chunks: chunks.map((chunk, index) => ({ index, sha256: sha256(chunk) })),
98
+ };
99
+ }
100
+
101
+ /** The unique canonical completion independently declares encoded and decoded aggregate ceilings. */
102
+ export function completionManifest(body, digest, version) {
103
+ const text = body.split("\n").slice(1).join("\n");
104
+ const manifest = JSON.parse(text);
105
+ keys(manifest, [
106
+ "version",
107
+ "originDescriptorSha256",
108
+ "payloadSha256",
109
+ "bytes",
110
+ "chunks",
111
+ ...(version === 2 ? ["encoding", "transportSha256", "transportBytes"] : []),
112
+ ]);
113
+ required(
114
+ canonicalJson(manifest) === text &&
115
+ manifest.version === version &&
116
+ manifest.originDescriptorSha256 === digest &&
117
+ typeof manifest.payloadSha256 === "string" &&
118
+ HEX.test(manifest.payloadSha256),
119
+ "checkpoint manifest differs"
120
+ );
121
+ required(
122
+ Number.isSafeInteger(manifest.bytes) &&
123
+ manifest.bytes > 0 &&
124
+ manifest.bytes <= PAYLOAD_MAXIMUM &&
125
+ Array.isArray(manifest.chunks) &&
126
+ manifest.chunks.length > 0 &&
127
+ manifest.chunks.length <= 256,
128
+ "checkpoint manifest bounds differ"
129
+ );
130
+ required(
131
+ version === 1 ||
132
+ (manifest.encoding === "deflate" &&
133
+ typeof manifest.transportSha256 === "string" &&
134
+ HEX.test(manifest.transportSha256) &&
135
+ Number.isSafeInteger(manifest.transportBytes) &&
136
+ manifest.transportBytes > 0 &&
137
+ manifest.transportBytes <= TRANSPORT_MAXIMUM),
138
+ "checkpoint encoded manifest differs"
139
+ );
140
+ return manifest;
141
+ }
@@ -1,75 +1,43 @@
1
1
  // This file is managed by Lisa and IS replaced on each `lisa` run.
2
2
  // Do not edit directly — durable changes belong upstream in Lisa.
3
3
 
4
- /** Bounded public checkpoint storage is data only, never provenance authority. */
5
- import { TextDecoder } from "node:util";
4
+ /**
5
+ * @file npm-update-checkpoint.mjs
6
+ * @description Bounded compressed public storage is data only, never provenance authority.
7
+ * @module npm-updater
8
+ */
6
9
  import { canonicalJson } from "../lisa-automation-provenance.mjs";
7
10
  import { keys, required } from "./npm-update-contract.mjs";
8
11
  import { sha256 } from "./github-attestation-verifier.mjs";
12
+ import {
13
+ checkpointPayloadBytes,
14
+ encodeCheckpointBytes,
15
+ decodeCheckpointBytes,
16
+ manifestFor,
17
+ completionManifest,
18
+ } from "./npm-update-checkpoint-codec.mjs";
9
19
 
10
- const MAXIMUM = 4_194_304;
11
20
  const CHUNK = 16_384;
12
21
  const PREFIX = "[lisa-npm-checkpoint]";
13
22
  const HEX = /^[a-f0-9]{64}$/;
14
23
 
15
- /** Closed public payloads contain no bearer token or executable extension field. */
16
- function payloadBytes(payload) {
17
- keys(payload, ["version", "proposal", "allocation", "preview", "bundle"]);
18
- required(
19
- payload.version === 1 && typeof payload.bundle === "string",
20
- "invalid public checkpoint payload"
21
- );
22
- const bytes = Buffer.from(`${canonicalJson(payload)}\n`);
23
- required(bytes.length <= MAXIMUM, "checkpoint payload exceeds bound");
24
- required(
25
- !/\beyJ[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\b/.test(
26
- payload.bundle
27
- ),
28
- "bearer JWT cannot be retained"
29
- );
30
- return bytes;
31
- }
32
-
33
- /** Numbered digest-checked chunks precede one exact completion manifest. */
34
- export function checkpointComments(payload, originDigest) {
24
+ /** Recognize closed wire versions, rejecting mixed or unknown formats for the same digest. */
25
+ function relevant(comments, digest) {
35
26
  required(
36
- typeof originDigest === "string" && HEX.test(originDigest),
37
- "invalid checkpoint origin"
38
- );
39
- const bytes = payloadBytes(payload);
40
- const count = Math.ceil(bytes.length / CHUNK);
41
- required(count > 0 && count <= 256, "checkpoint chunk count exceeds bound");
42
- const chunks = Array.from({ length: count }, (_, index) =>
43
- bytes.subarray(index * CHUNK, (index + 1) * CHUNK)
27
+ typeof digest === "string" && HEX.test(digest),
28
+ "invalid checkpoint digest"
44
29
  );
45
- const manifest = {
46
- version: 1,
47
- originDescriptorSha256: originDigest,
48
- payloadSha256: sha256(bytes),
49
- bytes: bytes.length,
50
- chunks: chunks.map((chunk, index) => ({ index, sha256: sha256(chunk) })),
51
- };
52
- return [
53
- ...chunks.map(
54
- (chunk, index) =>
55
- `${PREFIX} v1 ${originDigest} chunk ${index}/${count} ${sha256(chunk)}\n${chunk.toString("base64")}`
56
- ),
57
- `${PREFIX} v1 ${originDigest} complete\n${canonicalJson(manifest)}`,
58
- ];
59
- }
60
-
61
- /** A strict marker locates data only; signatures are still required separately. */
62
- function relevant(comments, digest) {
63
30
  required(
64
31
  Array.isArray(comments) && comments.length <= 2000,
65
32
  "checkpoint comment inventory exceeds bound"
66
33
  );
67
- return comments
68
- .filter(
69
- comment =>
70
- typeof comment.body === "string" &&
71
- comment.body.startsWith(`${PREFIX} v1 ${digest} `)
72
- )
34
+ const bodies = comments
35
+ .filter(comment => {
36
+ const match = /^\[lisa-npm-checkpoint\] (\S+) ([a-f0-9]{64})(?: |$)/.exec(
37
+ comment.body ?? ""
38
+ );
39
+ return match?.[2] === digest;
40
+ })
73
41
  .map(comment => {
74
42
  required(
75
43
  Buffer.byteLength(comment.body) <= 24_576,
@@ -77,59 +45,62 @@ function relevant(comments, digest) {
77
45
  );
78
46
  return comment.body;
79
47
  });
48
+ const versions = new Set(bodies.map(body => body.split(" ")[1]));
49
+ required(
50
+ versions.size <= 1 &&
51
+ [...versions].every(version => version === "v1" || version === "v2"),
52
+ "checkpoint transport version is unknown or mixed"
53
+ );
54
+ return bodies;
80
55
  }
81
56
 
82
- /** The unique completion record independently bounds the declared aggregate. */
83
- function completionManifest(body, digest) {
84
- const text = body.split("\n").slice(1).join("\n");
85
- const manifest = JSON.parse(text);
86
- keys(manifest, [
87
- "version",
88
- "originDescriptorSha256",
89
- "payloadSha256",
90
- "bytes",
91
- "chunks",
92
- ]);
57
+ /** New writes compress; an explicitly selected legacy retry preserves its original wire bytes. */
58
+ export function checkpointComments(payload, originDigest, version = 2) {
93
59
  required(
94
- canonicalJson(manifest) === text &&
95
- manifest.version === 1 &&
96
- manifest.originDescriptorSha256 === digest &&
97
- HEX.test(manifest.payloadSha256),
98
- "checkpoint manifest differs"
60
+ typeof originDigest === "string" && HEX.test(originDigest),
61
+ "invalid checkpoint origin"
99
62
  );
100
- required(
101
- Number.isSafeInteger(manifest.bytes) &&
102
- manifest.bytes > 0 &&
103
- manifest.bytes <= MAXIMUM &&
104
- Array.isArray(manifest.chunks) &&
105
- manifest.chunks.length > 0 &&
106
- manifest.chunks.length <= 256,
107
- "checkpoint manifest bounds differ"
63
+ const bytes = checkpointPayloadBytes(payload);
64
+ const transport = encodeCheckpointBytes(bytes, version);
65
+ const count = Math.ceil(transport.length / CHUNK);
66
+ required(count > 0 && count <= 256, "checkpoint chunk count exceeds bound");
67
+ const chunks = Array.from({ length: count }, (_, index) =>
68
+ transport.subarray(index * CHUNK, (index + 1) * CHUNK)
108
69
  );
109
- return manifest;
70
+ return [
71
+ ...chunks.map(
72
+ (chunk, index) =>
73
+ `${PREFIX} v${version} ${originDigest} chunk ${index}/${count} ${sha256(chunk)}\n${chunk.toString("base64")}`
74
+ ),
75
+ `${PREFIX} v${version} ${originDigest} complete\n${canonicalJson(manifestFor(bytes, transport, chunks, originDigest, version))}`,
76
+ ];
110
77
  }
111
78
 
112
- /** Each numbered chunk is canonical, bounded and byte-bound before aggregation. */
113
- function decodedChunks(bodies, digest, manifest) {
79
+ /** Each chunk is canonical, bounded and byte-bound before any decompression. */
80
+ function decodedChunks(bodies, digest, version, expectedCount) {
114
81
  const chunks = new Map();
82
+ let count = expectedCount;
115
83
  for (const body of bodies) {
116
84
  const match =
117
- /^\[lisa-npm-checkpoint\] v1 ([a-f0-9]{64}) chunk (0|[1-9]\d*)\/([1-9]\d*) ([a-f0-9]{64})\n([A-Za-z0-9+/]+=*)$/.exec(
85
+ /^\[lisa-npm-checkpoint\] v([12]) ([a-f0-9]{64}) chunk (0|[1-9]\d*)\/([1-9]\d*) ([a-f0-9]{64})\n([A-Za-z0-9+/]+=*)$/.exec(
118
86
  body
119
87
  );
120
88
  required(
121
- match &&
122
- match[1] === digest &&
123
- Number(match[3]) === manifest.chunks.length,
89
+ match && Number(match[1]) === version && match[2] === digest,
124
90
  "checkpoint chunk shape differs"
125
91
  );
126
- const index = Number(match[2]);
127
- const bytes = Buffer.from(match[5], "base64");
92
+ count ??= Number(match[4]);
93
+ const index = Number(match[3]);
94
+ const bytes = Buffer.from(match[6], "base64");
128
95
  required(
129
- index < manifest.chunks.length &&
96
+ count > 0 &&
97
+ count <= 256 &&
98
+ Number(match[4]) === count &&
99
+ index < count &&
100
+ bytes.length > 0 &&
130
101
  bytes.length <= CHUNK &&
131
- bytes.toString("base64") === match[5] &&
132
- sha256(bytes) === match[4],
102
+ bytes.toString("base64") === match[6] &&
103
+ sha256(bytes) === match[5],
133
104
  "checkpoint chunk bytes differ"
134
105
  );
135
106
  required(
@@ -138,52 +109,108 @@ function decodedChunks(bodies, digest, manifest) {
138
109
  );
139
110
  chunks.set(index, bytes);
140
111
  }
141
- return chunks;
112
+ required(
113
+ count > 0 && chunks.size === count,
114
+ "checkpoint missing or reordered chunk"
115
+ );
116
+ return Array.from({ length: count }, (_, index) => chunks.get(index));
142
117
  }
143
118
 
144
- /** Decode bounded canonical public storage; this function never authorizes recovery. */
119
+ /** Decode bounded canonical public storage; signatures are always required separately. */
145
120
  export function decodeCheckpoint(comments, digest) {
146
- required(
147
- typeof digest === "string" && HEX.test(digest),
148
- "invalid checkpoint digest"
149
- );
150
121
  const bodies = relevant(comments, digest);
151
122
  if (!bodies.length) return undefined;
123
+ const version = Number(bodies[0].split(" ")[1].slice(1));
152
124
  const complete = bodies.filter(body =>
153
- body.startsWith(`${PREFIX} v1 ${digest} complete\n`)
125
+ body.startsWith(`${PREFIX} v${version} ${digest} complete\n`)
154
126
  );
155
127
  required(complete.length === 1, "checkpoint incomplete or ambiguous");
156
- const manifest = completionManifest(complete[0], digest);
128
+ const manifest = completionManifest(complete[0], digest, version);
157
129
  const chunks = decodedChunks(
158
130
  bodies.filter(body => body !== complete[0]),
159
131
  digest,
160
- manifest
132
+ version,
133
+ manifest.chunks.length
161
134
  );
162
- const ordered = manifest.chunks.map((entry, index) => {
135
+ manifest.chunks.forEach((entry, index) => {
163
136
  keys(entry, ["index", "sha256"]);
164
- const chunk = chunks.get(index);
165
137
  required(
166
- entry.index === index && chunk && sha256(chunk) === entry.sha256,
138
+ entry.index === index && sha256(chunks[index]) === entry.sha256,
167
139
  "checkpoint missing or reordered chunk"
168
140
  );
169
- return chunk;
170
141
  });
171
- const bytes = Buffer.concat(ordered);
142
+ const transport = Buffer.concat(chunks);
143
+ required(
144
+ version === 1 ||
145
+ (transport.length === manifest.transportBytes &&
146
+ sha256(transport) === manifest.transportSha256),
147
+ "checkpoint encoded aggregate differs"
148
+ );
149
+ const { payload, bytes } = decodeCheckpointBytes(transport, version);
172
150
  required(
173
151
  bytes.length === manifest.bytes && sha256(bytes) === manifest.payloadSha256,
174
152
  "checkpoint aggregate differs"
175
153
  );
176
- const payload = JSON.parse(
177
- new TextDecoder("utf-8", { fatal: true, ignoreBOM: true }).decode(bytes)
154
+ return payload;
155
+ }
156
+
157
+ /** Complete chunk material retains its observed encoding instead of depending on recompression equality. */
158
+ function partialCompletion(comments, digest) {
159
+ const bodies = relevant(comments, digest);
160
+ required(
161
+ bodies.length > 0,
162
+ "partial checkpoint has missing original material"
178
163
  );
164
+ const version = Number(bodies[0].split(" ")[1].slice(1));
165
+ const chunks = decodedChunks(bodies, digest, version);
166
+ const transport = Buffer.concat(chunks);
167
+ const { payload, bytes } = decodeCheckpointBytes(transport, version);
168
+ const completion = `${PREFIX} v${version} ${digest} complete\n${canonicalJson(manifestFor(bytes, transport, chunks, digest, version))}`;
169
+ return { payload, completion };
170
+ }
171
+
172
+ /** Missing completion is recoverable data, never a successful durable authorization. */
173
+ export function recoverPartialCheckpoint(comments, digest) {
174
+ const recovered = partialCompletion(comments, digest);
175
+ return decodeCheckpoint(
176
+ [...comments, { body: recovered.completion }],
177
+ digest
178
+ );
179
+ }
180
+
181
+ /** Exact retries preserve observed versions; alternate valid compression is retained when all chunks exist. */
182
+ function writeBodies(payload, digest, existing) {
183
+ const observed = relevant(existing, digest);
184
+ if (!observed.length) return checkpointComments(payload, digest);
185
+ const version = Number(observed[0].split(" ")[1].slice(1));
186
+ const complete = observed.some(body =>
187
+ body.startsWith(`${PREFIX} v${version} ${digest} complete\n`)
188
+ );
189
+ if (complete) {
190
+ required(
191
+ canonicalJson(decodeCheckpoint(existing, digest)) ===
192
+ canonicalJson(payload),
193
+ "checkpoint retry payload differs"
194
+ );
195
+ return [...new Set(observed)].sort((left, right) => {
196
+ const index = body =>
197
+ body.includes(" complete\n")
198
+ ? Infinity
199
+ : Number(body.split(" ")[4].split("/")[0]);
200
+ return index(left) - index(right);
201
+ });
202
+ }
203
+ const expected = checkpointComments(payload, digest, version);
204
+ if (observed.every(body => expected.includes(body))) return expected;
205
+ const recovered = partialCompletion(existing, digest);
179
206
  required(
180
- payloadBytes(payload).equals(bytes),
181
- "checkpoint is noncanonical or duplicate-keyed"
207
+ canonicalJson(recovered.payload) === canonicalJson(payload),
208
+ "checkpoint retry payload differs"
182
209
  );
183
- return payload;
210
+ return [...new Set(observed), recovered.completion];
184
211
  }
185
212
 
186
- /** Readback before completion prevents a stored marker blessing missing bytes. */
213
+ /** Every write still requires current authority and immediate exact provider readback. */
187
214
  export async function persistCheckpoint(
188
215
  api,
189
216
  number,
@@ -191,14 +218,34 @@ export async function persistCheckpoint(
191
218
  digest,
192
219
  authorize
193
220
  ) {
194
- const bodies = checkpointComments(payload, digest);
195
221
  const endpoint = `repos/${api.policy.repository}/issues/${number}/comments`;
196
222
  const issueUrl = `https://api.github.com/repos/${api.policy.repository}/issues/${number}`;
223
+ await authorize();
224
+ let existing = await api.list(endpoint);
225
+ const bodies = writeBodies(payload, digest, existing);
197
226
  for (const body of bodies) {
227
+ const selected = relevant(existing, digest);
228
+ required(
229
+ new Set(selected).size === selected.length &&
230
+ selected.every(value => bodies.includes(value)),
231
+ "checkpoint retry transport differs"
232
+ );
233
+ let matches = existing.filter(comment => comment.body === body);
234
+ required(matches.length <= 1, "duplicate checkpoint write state");
235
+ if (matches.length) continue;
236
+ // Existing exact bodies require no mutation. Missing bodies always renew live authority
237
+ // and inventory first, including when completion is the sole remaining legacy write.
198
238
  await authorize();
199
- const existing = await api.list(endpoint);
200
- const matches = existing.filter(comment => comment.body === body);
201
- if (body.startsWith(`${PREFIX} v1 ${digest} complete\n`)) {
239
+ existing = await api.list(endpoint);
240
+ const refreshed = relevant(existing, digest);
241
+ required(
242
+ new Set(refreshed).size === refreshed.length &&
243
+ refreshed.every(value => bodies.includes(value)),
244
+ "checkpoint retry transport differs"
245
+ );
246
+ matches = existing.filter(comment => comment.body === body);
247
+ required(matches.length <= 1, "duplicate checkpoint write state");
248
+ if (/ complete\n/.test(body)) {
202
249
  const projected = matches.length ? existing : [...existing, { body }];
203
250
  required(
204
251
  canonicalJson(decodeCheckpoint(projected, digest)) ===
@@ -206,7 +253,6 @@ export async function persistCheckpoint(
206
253
  "checkpoint chunks must read back before completion"
207
254
  );
208
255
  }
209
- required(matches.length <= 1, "duplicate checkpoint write state");
210
256
  if (!matches.length) {
211
257
  const created = await api.request(endpoint, "POST", { body });
212
258
  required(
@@ -46,6 +46,7 @@ const PRODUCER = [
46
46
  "cancellation",
47
47
  "cancellation-proof",
48
48
  "checkpoint",
49
+ "checkpoint-codec",
49
50
  "classifier-cache",
50
51
  "contract",
51
52
  "controller-broker",
@@ -23,8 +23,8 @@ import { persistCheckpoint } from "./npm-update-checkpoint.mjs";
23
23
  import { required, validateProposal } from "./npm-update-contract.mjs";
24
24
 
25
25
  import {
26
- checkpointComments,
27
26
  decodeCheckpoint,
27
+ recoverPartialCheckpoint,
28
28
  } from "./npm-update-checkpoint.mjs";
29
29
 
30
30
  /** A new issuer binds current authority to the immutable original artifact and destination. */
@@ -66,68 +66,12 @@ export function recoveryRecord(
66
66
  };
67
67
  }
68
68
 
69
- /** Complete untrusted chunks can restore missing transport metadata, never authority. */
70
- export function recoverPartialCheckpoint(comments, digest) {
71
- required(
72
- Array.isArray(comments) && comments.length <= 2000,
73
- "partial checkpoint inventory exceeds bound"
74
- );
75
- const bodies = comments.filter(comment =>
76
- comment.body?.startsWith(`[lisa-npm-checkpoint] v1 ${digest} `)
77
- );
78
- const chunks = new Map();
79
- let count;
80
- for (const { body } of bodies) {
81
- const match =
82
- /^\[lisa-npm-checkpoint\] v1 ([a-f0-9]{64}) chunk (0|[1-9]\d*)\/([1-9]\d*) ([a-f0-9]{64})\n([A-Za-z0-9+/]+=*)$/.exec(
83
- body
84
- );
85
- required(
86
- match && Buffer.byteLength(body) <= 24_576 && match[1] === digest,
87
- "partial checkpoint chunk differs"
88
- );
89
- count ??= Number(match[3]);
90
- const index = Number(match[2]);
91
- const bytes = Buffer.from(match[5], "base64");
92
- required(
93
- count <= 256 &&
94
- Number(match[3]) === count &&
95
- index < count &&
96
- bytes.length <= 16_384 &&
97
- bytes.toString("base64") === match[5] &&
98
- sha256(bytes) === match[4],
99
- "partial checkpoint bounds or digest differ"
100
- );
101
- required(
102
- !chunks.has(index) || chunks.get(index).equals(bytes),
103
- "partial checkpoint has differing duplicate chunk"
104
- );
105
- chunks.set(index, bytes);
106
- }
107
- required(
108
- count > 0 && chunks.size === count,
109
- "partial checkpoint has missing original material"
110
- );
111
- const bytes = Buffer.concat(
112
- Array.from({ length: count }, (_, index) => chunks.get(index))
113
- );
114
- required(
115
- bytes.length <= 4_194_304,
116
- "partial checkpoint exceeds aggregate bound"
117
- );
118
- const payload = JSON.parse(
119
- new TextDecoder("utf-8", { fatal: true, ignoreBOM: true }).decode(bytes)
120
- );
121
- const complete = checkpointComments(payload, digest).at(-1);
122
- return decodeCheckpoint([...comments, { body: complete }], digest);
123
- }
124
-
125
69
  /** Unsigned draft bytes aid retry; only the separately verified origin can authorize gates. */
126
70
  export function locateCheckpoint(comments, proposal, policy) {
127
71
  const digests = new Set(
128
72
  comments.flatMap(comment => {
129
73
  const match =
130
- /^\[lisa-npm-checkpoint\] v1 ([a-f0-9]{64}) (?:chunk|complete)\b/.exec(
74
+ /^\[lisa-npm-checkpoint\] \S+ ([a-f0-9]{64}) (?:chunk|complete)\b/.exec(
131
75
  comment.body ?? ""
132
76
  );
133
77
  return match && match[1] !== proposal.key ? [match[1]] : [];
@@ -136,10 +80,14 @@ export function locateCheckpoint(comments, proposal, policy) {
136
80
  required(digests.size <= 1, "multiple origin checkpoints are preserved");
137
81
  const digest = [...digests][0] ?? proposal.key;
138
82
  const present = comments.filter(comment =>
139
- comment.body?.startsWith(`[lisa-npm-checkpoint] v1 ${digest} `)
83
+ new RegExp(`^\\[lisa-npm-checkpoint\\] \\S+ ${digest} `).test(
84
+ comment.body ?? ""
85
+ )
140
86
  );
141
87
  const complete = present.some(comment =>
142
- comment.body.startsWith(`[lisa-npm-checkpoint] v1 ${digest} complete\n`)
88
+ new RegExp(`^\\[lisa-npm-checkpoint\\] \\S+ ${digest} complete\\n`).test(
89
+ comment.body
90
+ )
143
91
  );
144
92
  const payload = complete
145
93
  ? decodeCheckpoint(comments, digest)
@@ -167,6 +115,7 @@ export {
167
115
  checkpointComments,
168
116
  decodeCheckpoint,
169
117
  persistCheckpoint,
118
+ recoverPartialCheckpoint,
170
119
  } from "./npm-update-checkpoint.mjs";
171
120
 
172
121
  /** Renewal verifies fresh authority against every immutable original identity field. */