@1aboveio/skills 1.0.18 → 1.0.20

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 (89) hide show
  1. package/.codex-plugin/plugin.json +1 -1
  2. package/.codex-plugin/resources.json +151 -145
  3. package/package.json +4 -4
  4. package/skills/app-dev/airflow/SKILL.md +1 -1
  5. package/skills/app-dev/android-development/SKILL.md +1 -1
  6. package/skills/app-dev/app-debug/SKILL.md +1 -1
  7. package/skills/app-dev/better-auth/SKILL.md +1 -1
  8. package/skills/app-dev/data-catalog/SKILL.md +1 -1
  9. package/skills/app-dev/data-quality/SKILL.md +1 -1
  10. package/skills/app-dev/ios-foundation/SKILL.md +1 -1
  11. package/skills/app-dev/maestro/SKILL.md +1 -1
  12. package/skills/app-dev/monorepo/SKILL.md +1 -1
  13. package/skills/app-dev/nextjs-fullstack/SKILL.md +1 -1
  14. package/skills/app-dev/prisma-setup/SKILL.md +1 -1
  15. package/skills/app-dev/pyspark/SKILL.md +1 -1
  16. package/skills/app-dev/python-backend/SKILL.md +1 -1
  17. package/skills/app-dev/shadcn/SKILL.md +1 -1
  18. package/skills/app-dev/zod-v4/SKILL.md +1 -1
  19. package/skills/cicd/cloud-build/SKILL.md +1 -1
  20. package/skills/cicd/cloud-debug/SKILL.md +1 -1
  21. package/skills/cicd/cloud-deploy/SKILL.md +1 -1
  22. package/skills/cicd/docker/SKILL.md +1 -1
  23. package/skills/cicd/github-actions/SKILL.md +1 -1
  24. package/skills/cicd/github-flow/SKILL.md +1 -1
  25. package/skills/cicd/google-cloud/SKILL.md +1 -1
  26. package/skills/cicd/mergify/SKILL.md +1 -1
  27. package/skills/cicd/podman/SKILL.md +1 -1
  28. package/skills/content/article-review/SKILL.md +1 -1
  29. package/skills/content/article-to-image/SKILL.md +1 -1
  30. package/skills/content/flowchart-svg/SKILL.md +1 -1
  31. package/skills/content/image-gen/SKILL.md +1 -1
  32. package/skills/content/mermaid/SKILL.md +1 -1
  33. package/skills/content/wechat-article-writer/SKILL.md +1 -1
  34. package/skills/content/wechat-publisher/SKILL.md +1 -1
  35. package/skills/content/wechat-search/SKILL.md +1 -1
  36. package/skills/content/xai-search/SKILL.md +1 -1
  37. package/skills/core/distill-writing/SKILL.md +1 -1
  38. package/skills/core/doc-to-md/SKILL.md +1 -1
  39. package/skills/core/editorial-review/SKILL.md +1 -1
  40. package/skills/core/house-style/SKILL.md +1 -1
  41. package/skills/core/plain-language/SKILL.md +1 -1
  42. package/skills/core/polish-cn/SKILL.md +1 -1
  43. package/skills/core/reg-parser/SKILL.md +1 -1
  44. package/skills/core/terminology/SKILL.md +1 -1
  45. package/skills/core/write-doc/SKILL.md +1 -1
  46. package/skills/core/write-report/SKILL.md +1 -1
  47. package/skills/engineering/better-review/SKILL.md +5 -4
  48. package/skills/engineering/better-review/references/passes.md +15 -0
  49. package/skills/engineering/better-review/references/result.md +14 -0
  50. package/skills/engineering/e2e-test/SKILL.md +1 -1
  51. package/skills/engineering/engineering/SKILL.md +1 -1
  52. package/skills/engineering/engineering-runtime/SKILL.md +1 -1
  53. package/skills/engineering/engineering-runtime/coherence/workflow.json +13 -13
  54. package/skills/engineering/ensure-coverage/SKILL.md +1 -1
  55. package/skills/engineering/golden-path/SKILL.md +1 -1
  56. package/skills/engineering/harness-runtime/SKILL.md +1 -1
  57. package/skills/engineering/resolve-issues/SKILL.md +14 -9
  58. package/skills/engineering/resolve-issues/references/ci.md +3 -1
  59. package/skills/engineering/resolve-issues/references/delivery-progress.md +16 -0
  60. package/skills/engineering/resolve-issues/references/done.md +7 -0
  61. package/skills/engineering/resolve-issues/references/dropbox.md +31 -6
  62. package/skills/engineering/resolve-issues/references/expire.md +6 -3
  63. package/skills/engineering/resolve-issues/references/implementation.md +67 -6
  64. package/skills/engineering/resolve-issues/references/preflight.md +17 -3
  65. package/skills/engineering/resolve-issues/references/retention.md +162 -0
  66. package/skills/engineering/resolve-issues/references/review.md +42 -9
  67. package/skills/engineering/resolve-issues/references/task-plan.md +17 -2
  68. package/skills/engineering/resolve-issues/references/workspaces.md +40 -10
  69. package/skills/engineering/resolve-issues/scripts/daily-cleanup.mjs +169 -0
  70. package/skills/engineering/resolve-issues/scripts/dropbox.mjs +2 -1
  71. package/skills/engineering/resolve-issues/scripts/next-actions.mjs +79 -4
  72. package/skills/engineering/resolve-issues/scripts/resource-formats.mjs +625 -0
  73. package/skills/engineering/resolve-issues/scripts/retention-lock.mjs +548 -0
  74. package/skills/engineering/resolve-issues/scripts/retention-plans.mjs +57 -0
  75. package/skills/engineering/resolve-issues/scripts/retention.mjs +2314 -0
  76. package/skills/engineering/resolve-issues/scripts/review-verdict.mjs +63 -10
  77. package/skills/engineering/resolve-release/SKILL.md +1 -1
  78. package/skills/engineering/rush-issues/SKILL.md +1 -1
  79. package/skills/engineering/rush-release/SKILL.md +1 -1
  80. package/skills/engineering/test-quality/SKILL.md +1 -1
  81. package/skills/risk-compliance/compliance-audit/SKILL.md +1 -1
  82. package/skills/risk-compliance/contract-compare/SKILL.md +1 -1
  83. package/skills/risk-compliance/contract-review/SKILL.md +1 -1
  84. package/skills/risk-compliance/doc-authenticity/SKILL.md +1 -1
  85. package/skills/risk-compliance/fraud-detection/SKILL.md +1 -1
  86. package/skills/risk-compliance/payment-analysis/SKILL.md +1 -1
  87. package/skills/risk-compliance/portfolio-payment-analysis/SKILL.md +1 -1
  88. package/skills/risk-compliance/similar-domain/SKILL.md +1 -1
  89. package/skills/risk-compliance/tld-detection/SKILL.md +1 -1
@@ -0,0 +1,625 @@
1
+ // Typed proof for generated resource formats and the source-owned inputs that rebuild them.
2
+ //
3
+ // retention.mjs is deliberately strict in two places: a run-metadata name such as
4
+ // `source`, `sources`, `results` or `raw` is never disposable, and a symbolic link that
5
+ // points outside the target blocks removal. Both rules are right for a generic resource
6
+ // and wrong for a proven package-manager or build artifact: real installed dependencies
7
+ // ship a `source/` directory, and a virtual environment's `bin/python` is a reference to
8
+ // the base interpreter by design. A blanket name guard therefore rejects genuine vendor
9
+ // content, which is the reviewed defect this module narrows.
10
+ //
11
+ // This module is the narrow, read-only exception. It proves one typed format plus the
12
+ // canonical, source-owned rebuild inputs that reproduce it, and reports which two
13
+ // generic guards the caller may relax for that resource. It never deletes, registers,
14
+ // quarantines, or prints a rebuild command, and it never executes anything: proof is
15
+ // bounded lstat/readdir/hash work plus, for two manifests, JSON.parse. External symbolic
16
+ // links inside a proven format are REFERENCES, so cleanup unlinks the link without
17
+ // following it; no code here traverses one. Credential policy is not this module's: the
18
+ // engine's credential guard stays in force for every resource, typed or not.
19
+ //
20
+ // API
21
+ // inspectResourceFormat({ format, path, inputs, inspection? })
22
+ // -> { ok, state, reason, format, root, markers, inputProofs,
23
+ // allowEmbeddedSourceNames, allowReferenceSymlinks,
24
+ // embeddedSourceNames, blockingMetadata, referenceSymlinks, retainInputs }
25
+ // Re-proves the markers and proves the rebuild inputs from disk. `inspection` is
26
+ // optional: pass the caller's own tree inspection ({ ok, reason, metadataNames,
27
+ // externalSymlinks }) so the name verdict and the external-link verdict reuse one
28
+ // walk. With no inspection, no name or link scan happens here at all.
29
+ // validateRebuildInputs(inputProofs)
30
+ // -> { ok, reason, checked, changed: [{ name, path, kind, detail }] }
31
+ // Re-reads each recorded input and reports missing / path / identity / hardlink /
32
+ // content changes against the recorded proof. Read-only.
33
+ // classifyRebuildState({ format, path, inputProofs, inspection? })
34
+ // -> { ok, state: 'ready'|'missing'|'partial'|'inputs-changed'|'unproven', ... }
35
+ // Re-proves the markers (cheap, targeted) and validates the recorded inputs, without
36
+ // writing. A root that looks present but is not usable after a manual cleanup stays
37
+ // 'partial', never 'ready'. Pass `inspection` too and run metadata inside the tree
38
+ // keeps the same 'partial' verdict inspectResourceFormat reports.
39
+ // classifyEmbeddedNames({ format, names })
40
+ // -> { format, allowEmbeddedSourceNames, allowed, blocking }
41
+ // Pure split of the caller's metadata names into the vendored names a typed format
42
+ // may embed and the run metadata that still blocks removal.
43
+ //
44
+ // Formats and the canonical rebuild inputs each one needs (all inputs must be canonical
45
+ // regular files outside the target, at most 4 inputs, at most 16 MiB each, one hard
46
+ // link, no symbolic link; a JSON manifest must parse to an object):
47
+ // pnpm-install <project>/node_modules .modules.yaml + .pnpm/<package@version>/
48
+ // package.json + pnpm-lock.yaml
49
+ // uv-venv <project>/.venv pyvenv.cfg + bin/python reference +
50
+ // lib/python*/site-packages
51
+ // pyproject.toml + uv.lock
52
+ // next-build <project>/.next BUILD_ID + build-manifest.json + server/
53
+ // package.json + pnpm-lock.yaml
54
+ // pnpm-store <project>/.pnpm-store v<N>/files/<bucket>/<content-addressed>
55
+ // package.json + pnpm-lock.yaml
56
+ // Anything else, including no format at all, is `generic`: no typed proof exists, `ok`
57
+ // is false, and both allowances are false, so the caller keeps its existing strict
58
+ // checks. A folder name never creates authority.
59
+ //
60
+ // The caller names the rebuild inputs. This module proves that the named files are the
61
+ // canonical ones for the format, sit outside the target, and are unchanged since the
62
+ // proof; it records their content hash and identity. It cannot decide that a particular
63
+ // lockfile is the one that produced a particular artifact, so choosing an owner's own
64
+ // inputs stays with the caller (retention.mjs, from the unit's project).
65
+ //
66
+ // Integration sketch (retention.mjs owns the real wiring):
67
+ // const proof = inspectResourceFormat({
68
+ // format: entry.resourceFormat, path: entry.path, inputs: rebuildInputsFor(entry),
69
+ // inspection,
70
+ // })
71
+ // if (!proof.ok) retain(entry, proof.reason) // generic rules, unchanged
72
+ // else remove(entry.path, { unlinkLinks: proof.referenceSymlinks }) // unlink, never follow
73
+
74
+ import { createHash } from 'node:crypto'
75
+ import {
76
+ closeSync, constants as fsConstants, fstatSync, lstatSync, openSync, readSync, readdirSync,
77
+ readlinkSync, realpathSync,
78
+ } from 'node:fs'
79
+ import { basename, isAbsolute, join, resolve } from 'node:path'
80
+ import { containsPath, normPath } from '../../harness-runtime/workspace-lifecycle.mjs'
81
+
82
+ // Finite bounds. The input budget is a ceiling for one proof, not a size policy: two
83
+ // source-owned configuration files comfortably fit, and a bound is still enforced.
84
+ export const MAX_REBUILD_INPUTS = 4
85
+ export const MAX_REBUILD_INPUT_BYTES = 16 * 1024 * 1024
86
+ const HASH_CHUNK_BYTES = 1024 * 1024
87
+ const MAX_MARKER_ENTRIES = 4096
88
+ const MAX_BUCKET_ENTRIES = 512
89
+ const MAX_BUCKETS_SCANNED = 64
90
+ const MAX_MANIFEST_BYTES = 16 * 1024 * 1024
91
+
92
+ // The four run-metadata names that real installed dependencies and build outputs
93
+ // legitimately contain. Only a proven typed format may embed these.
94
+ const VENDORED_SOURCE_NAMES = new Set(['source', 'sources', 'results', 'raw'])
95
+ // Run metadata that stays blocking even inside a proven typed format. A generated tree
96
+ // is never the owner of a plan, a finding record, a checkpoint, or a Git repository.
97
+ const RUN_METADATA_NAMES = new Set([
98
+ 'task-plan.json', 'issue-tree.json', 'taskplan.json', 'dropbox.json', 'findings.json',
99
+ 'next-actions-seen.json', 'decisions', 'findings', 'reviewverdicts', 'review-verdicts',
100
+ 'briefs', 'profiling', 'checkpoints', '.git',
101
+ ])
102
+
103
+ const FORMATS = Object.freeze({
104
+ 'pnpm-install': Object.freeze({
105
+ inputs: Object.freeze(['package.json', 'pnpm-lock.yaml']),
106
+ jsonInputs: Object.freeze(['package.json']),
107
+ }),
108
+ 'uv-venv': Object.freeze({
109
+ inputs: Object.freeze(['pyproject.toml', 'uv.lock']),
110
+ jsonInputs: Object.freeze([]),
111
+ }),
112
+ 'next-build': Object.freeze({
113
+ inputs: Object.freeze(['package.json', 'pnpm-lock.yaml']),
114
+ jsonInputs: Object.freeze(['package.json']),
115
+ }),
116
+ 'pnpm-store': Object.freeze({
117
+ inputs: Object.freeze(['package.json', 'pnpm-lock.yaml']),
118
+ jsonInputs: Object.freeze(['package.json']),
119
+ }),
120
+ })
121
+ export const TYPED_FORMATS = Object.freeze(Object.keys(FORMATS))
122
+
123
+ const CA_FILE_NAME = /^[0-9a-f]{32,}(?:[-._][A-Za-z0-9._-]+)?$/i
124
+ const O_NOFOLLOW = fsConstants.O_NOFOLLOW ?? 0
125
+
126
+ const isObject = (v) => v != null && typeof v === 'object' && !Array.isArray(v)
127
+ const nonEmpty = (v) => typeof v === 'string' && v.trim().length > 0
128
+ const real = (p) => (realpathSync.native ? realpathSync.native(p) : realpathSync(p))
129
+
130
+ function formatId(value) {
131
+ const id = typeof value === 'string' ? value.trim() : ''
132
+ return Object.hasOwn(FORMATS, id) ? id : 'generic'
133
+ }
134
+
135
+ function genericReason(requested) {
136
+ const named = typeof requested === 'string' && requested.trim() ? `'${requested.trim()}' is not a typed format` : 'no typed format was requested'
137
+ return `${named}; generic resources keep the existing strict metadata-name and external-link checks`
138
+ }
139
+
140
+ function verdict(fields) {
141
+ return {
142
+ ok: fields.ok === true,
143
+ state: fields.state,
144
+ reason: fields.reason ?? null,
145
+ format: fields.format ?? 'generic',
146
+ root: fields.root ?? null,
147
+ markers: fields.markers ?? [],
148
+ inputProofs: fields.inputProofs ?? null,
149
+ allowEmbeddedSourceNames: fields.allowEmbeddedSourceNames === true,
150
+ allowReferenceSymlinks: fields.allowReferenceSymlinks === true,
151
+ embeddedSourceNames: fields.embeddedSourceNames ?? [],
152
+ blockingMetadata: fields.blockingMetadata ?? [],
153
+ referenceSymlinks: fields.referenceSymlinks ?? [],
154
+ // Rebuild inputs are source-owned configuration: they are retained, never a
155
+ // deletion candidate, and this module only ever reads them.
156
+ retainInputs: true,
157
+ }
158
+ }
159
+
160
+ // ---- bounded, no-follow primitives ----
161
+
162
+ function marker(rel, present, detail) {
163
+ return { rel, present: present === true, detail: detail ?? null }
164
+ }
165
+
166
+ // readdir + Dirent types never follow a symbolic link, so a linked directory is reported
167
+ // as a link, not as the tree it points at.
168
+ function listDir(abs, maxEntries = MAX_MARKER_ENTRIES) {
169
+ let all
170
+ try { all = readdirSync(abs, { withFileTypes: true }) } catch (error) {
171
+ return { entries: [], truncated: false, error: error?.code ?? error?.message ?? 'unreadable' }
172
+ }
173
+ const sorted = all.slice().sort((a, b) => (a.name < b.name ? -1 : a.name > b.name ? 1 : 0))
174
+ return { entries: sorted.slice(0, maxEntries), truncated: sorted.length > maxEntries, error: null }
175
+ }
176
+
177
+ // Opens with O_NOFOLLOW so a link swapped in after the lstat cannot redirect the read,
178
+ // hashes the opened descriptor, and refuses a file that changes under it.
179
+ function readBounded(path, expected, maxBytes) {
180
+ let fd = null
181
+ try {
182
+ fd = openSync(path, fsConstants.O_RDONLY | O_NOFOLLOW)
183
+ const opened = fstatSync(fd)
184
+ if (!opened.isFile() || opened.dev !== expected.dev || opened.ino !== expected.ino) return { error: `${path} changed before it could be read` }
185
+ if (opened.size <= 0) return { error: `${path} is empty` }
186
+ if (opened.size > maxBytes) return { error: `${path} exceeds the ${maxBytes}-byte bound` }
187
+ const digest = createHash('sha256')
188
+ const chunks = []
189
+ const buffer = Buffer.allocUnsafe(HASH_CHUNK_BYTES)
190
+ let read = 0
191
+ while (read < opened.size) {
192
+ const got = readSync(fd, buffer, 0, Math.min(buffer.length, opened.size - read), read)
193
+ if (got <= 0) return { error: `${path} ended before its recorded size` }
194
+ digest.update(buffer.subarray(0, got))
195
+ chunks.push(Buffer.from(buffer.subarray(0, got)))
196
+ read += got
197
+ }
198
+ if (readSync(fd, Buffer.alloc(1), 0, 1, opened.size) !== 0) return { error: `${path} grew while it was being read` }
199
+ const final = fstatSync(fd)
200
+ if (!final.isFile() || final.dev !== opened.dev || final.ino !== opened.ino
201
+ || final.size !== opened.size || final.mtimeMs !== opened.mtimeMs) {
202
+ return { error: `${path} changed while it was being read` }
203
+ }
204
+ return {
205
+ buffer: Buffer.concat(chunks, read),
206
+ hash: digest.digest('hex'),
207
+ bytes: read,
208
+ identity: { dev: opened.dev, ino: opened.ino, size: opened.size, mtimeMs: opened.mtimeMs },
209
+ }
210
+ } catch (error) {
211
+ return { error: `cannot read ${path}: ${error?.code ?? error?.message}` }
212
+ } finally {
213
+ if (fd !== null) { try { closeSync(fd) } catch { /* already closed */ } }
214
+ }
215
+ }
216
+
217
+ // ---- marker proof ----
218
+
219
+ function probeFile(root, rel, markers, { requireContent = false, maxBytes = null } = {}) {
220
+ let st
221
+ try { st = lstatSync(join(root, rel)) } catch (error) {
222
+ if (error?.code === 'ENOENT') { markers.push(marker(rel, false, 'missing')); return { ok: false, reason: `${rel} is missing` } }
223
+ markers.push(marker(rel, false, `unreadable: ${error?.code ?? error?.message}`))
224
+ return { ok: false, reason: `cannot inspect ${rel}: ${error?.code ?? error?.message}` }
225
+ }
226
+ if (st.isSymbolicLink()) { markers.push(marker(rel, true, 'symbolic link')); return { ok: false, reason: `${rel} is a symbolic link; a linked marker cannot prove the format` } }
227
+ if (!st.isFile()) { markers.push(marker(rel, true, 'not a regular file')); return { ok: false, reason: `${rel} is not a regular file` } }
228
+ if (requireContent && st.size === 0) { markers.push(marker(rel, true, 'empty')); return { ok: false, reason: `${rel} is empty` } }
229
+ if (maxBytes !== null && st.size > maxBytes) { markers.push(marker(rel, true, 'oversize')); return { ok: false, reason: `${rel} exceeds the ${maxBytes}-byte bound` } }
230
+ markers.push(marker(rel, true, null))
231
+ return { ok: true, stat: st }
232
+ }
233
+
234
+ function probeDirectory(root, rel, markers, { requireEntry = false } = {}) {
235
+ let st
236
+ try { st = lstatSync(join(root, rel)) } catch (error) {
237
+ if (error?.code === 'ENOENT') { markers.push(marker(rel, false, 'missing')); return { ok: false, reason: `${rel} is missing` } }
238
+ markers.push(marker(rel, false, `unreadable: ${error?.code ?? error?.message}`))
239
+ return { ok: false, reason: `cannot inspect ${rel}: ${error?.code ?? error?.message}` }
240
+ }
241
+ if (st.isSymbolicLink()) { markers.push(marker(rel, true, 'symbolic link')); return { ok: false, reason: `${rel} is a symbolic link; a linked directory cannot prove the format` } }
242
+ if (!st.isDirectory()) { markers.push(marker(rel, true, 'not a directory')); return { ok: false, reason: `${rel} is not a directory` } }
243
+ const listed = listDir(join(root, rel))
244
+ if (listed.error) {
245
+ markers.push(marker(rel, true, `unreadable: ${listed.error}`))
246
+ return { ok: false, reason: `cannot enumerate ${rel}: ${listed.error}` }
247
+ }
248
+ if (requireEntry && listed.entries.length === 0) { markers.push(marker(rel, true, 'empty')); return { ok: false, reason: `${rel} is empty` } }
249
+ markers.push(marker(rel, true, listed.truncated ? `listing bound of ${MAX_MARKER_ENTRIES} entries reached` : null))
250
+ return { ok: true, entries: listed.entries, truncated: listed.truncated }
251
+ }
252
+
253
+ function provePnpmInstall(root, markers) {
254
+ const modules = probeFile(root, '.modules.yaml', markers, { requireContent: true })
255
+ if (!modules.ok) return modules
256
+ const virtual = probeDirectory(root, '.pnpm', markers)
257
+ if (!virtual.ok) return virtual
258
+ // pnpm names each virtual-store entry <name>@<version> or @scope+<name>@<version>.
259
+ const installed = virtual.entries.find((entry) => entry.isDirectory() && entry.name.includes('@') && entry.name.length > 2)
260
+ markers.push(marker('.pnpm/<package@version>', Boolean(installed), installed ? installed.name : 'no installed package directory'))
261
+ if (!installed) {
262
+ return { ok: false, reason: `.pnpm holds no installed package directory${virtual.truncated ? ' within the listing bound' : ''}, so the install is not proven complete` }
263
+ }
264
+ return { ok: true }
265
+ }
266
+
267
+ // bin/python is expected to be a REFERENCE to the base interpreter. It is read as a link
268
+ // text and never resolved, opened, or stat'ed through.
269
+ function proveInterpreterReference(root, markers) {
270
+ const rel = 'bin/python'
271
+ const path = join(root, rel)
272
+ let st
273
+ try { st = lstatSync(path) } catch (error) {
274
+ if (error?.code === 'ENOENT') { markers.push(marker(rel, false, 'missing')); return { ok: false, reason: `${rel} is missing` } }
275
+ markers.push(marker(rel, false, `unreadable: ${error?.code ?? error?.message}`))
276
+ return { ok: false, reason: `cannot inspect ${rel}: ${error?.code ?? error?.message}` }
277
+ }
278
+ if (st.isSymbolicLink()) {
279
+ let target
280
+ try { target = readlinkSync(path) } catch (error) {
281
+ markers.push(marker(rel, true, 'unreadable link'))
282
+ return { ok: false, reason: `cannot read the interpreter reference ${rel}: ${error?.code ?? error?.message}` }
283
+ }
284
+ if (!nonEmpty(target)) {
285
+ markers.push(marker(rel, true, 'empty link'))
286
+ return { ok: false, reason: `${rel} is an empty symbolic link` }
287
+ }
288
+ markers.push(marker(rel, true, `interpreter reference -> ${target.length > 200 ? `${target.slice(0, 197)}...` : target}`))
289
+ return { ok: true, interpreter: { kind: 'reference', target } }
290
+ }
291
+ if (st.isFile() && st.size > 0) {
292
+ markers.push(marker(rel, true, 'interpreter copy'))
293
+ return { ok: true, interpreter: { kind: 'file' } }
294
+ }
295
+ markers.push(marker(rel, true, 'not an interpreter'))
296
+ return { ok: false, reason: `${rel} is neither an interpreter reference nor a non-empty interpreter file` }
297
+ }
298
+
299
+ function proveUvVenv(root, markers) {
300
+ const config = probeFile(root, 'pyvenv.cfg', markers, { requireContent: true })
301
+ if (!config.ok) return config
302
+ const bin = probeDirectory(root, 'bin', markers)
303
+ if (!bin.ok) return bin
304
+ const interpreter = proveInterpreterReference(root, markers)
305
+ if (!interpreter.ok) return interpreter
306
+ const lib = probeDirectory(root, 'lib', markers)
307
+ if (!lib.ok) return lib
308
+ const pythonDir = lib.entries.find((entry) => entry.isDirectory() && /^python\d/i.test(entry.name))
309
+ markers.push(marker('lib/python*/', Boolean(pythonDir), pythonDir ? pythonDir.name : 'no python directory'))
310
+ if (!pythonDir) return { ok: false, reason: 'lib holds no python<version> directory' }
311
+ const sitePackages = probeDirectory(root, `lib/${pythonDir.name}/site-packages`, markers)
312
+ if (!sitePackages.ok) return sitePackages
313
+ return { ok: true, interpreter: interpreter.interpreter }
314
+ }
315
+
316
+ function proveNextBuild(root, markers) {
317
+ const buildId = probeFile(root, 'BUILD_ID', markers, { requireContent: true })
318
+ if (!buildId.ok) return buildId
319
+ const manifest = probeFile(root, 'build-manifest.json', markers, { requireContent: true, maxBytes: MAX_MANIFEST_BYTES })
320
+ if (!manifest.ok) return manifest
321
+ const read = readBounded(join(root, 'build-manifest.json'), manifest.stat, MAX_MANIFEST_BYTES)
322
+ if (read.error) return { ok: false, reason: read.error }
323
+ let parsed
324
+ try { parsed = JSON.parse(read.buffer.toString('utf8')) } catch { return { ok: false, reason: 'build-manifest.json is not readable JSON' } }
325
+ if (!isObject(parsed)) return { ok: false, reason: 'build-manifest.json does not contain a JSON object' }
326
+ const server = probeDirectory(root, 'server', markers, { requireEntry: true })
327
+ if (!server.ok) return server
328
+ return { ok: true }
329
+ }
330
+
331
+ // A content-addressed name is a long hex digest, optionally with pnpm's -index.json or
332
+ // executable suffix. Bucket scanning is capped so a large store cannot turn this into a
333
+ // walk; a store whose first bounded buckets hold no file stays unproven.
334
+ function findContentAddressedEntry(dir, entries) {
335
+ let scanned = 0
336
+ for (const entry of entries) {
337
+ if (entry.isFile() && CA_FILE_NAME.test(entry.name)) return entry.name
338
+ if (!entry.isDirectory() || !/^[0-9a-f]{2}$/i.test(entry.name)) continue
339
+ if (++scanned > MAX_BUCKETS_SCANNED) return null
340
+ const bucket = listDir(join(dir, entry.name), MAX_BUCKET_ENTRIES)
341
+ if (bucket.error) continue
342
+ const hit = bucket.entries.find((child) => child.isFile() && CA_FILE_NAME.test(child.name))
343
+ if (hit) return `${entry.name}/${hit.name}`
344
+ }
345
+ return null
346
+ }
347
+
348
+ function provePnpmStore(root, markers) {
349
+ const listing = listDir(root)
350
+ if (listing.error) return { ok: false, reason: `cannot enumerate the store root: ${listing.error}` }
351
+ const versions = listing.entries
352
+ .filter((entry) => entry.isDirectory() && /^v\d+$/.test(entry.name))
353
+ .map((entry) => entry.name)
354
+ .sort((a, b) => Number(b.slice(1)) - Number(a.slice(1)) || (a < b ? 1 : a > b ? -1 : 0))
355
+ if (!versions.length) return { ok: false, reason: 'no versioned v<N> store directory at the root' }
356
+ const versionDir = versions[0]
357
+ markers.push(marker(versionDir, true, `store version ${versionDir.slice(1)}`))
358
+ const files = probeDirectory(root, `${versionDir}/files`, markers)
359
+ if (!files.ok) return files
360
+ const sample = findContentAddressedEntry(join(root, versionDir, 'files'), files.entries)
361
+ markers.push(marker(`${versionDir}/files/<content-addressed>`, Boolean(sample), sample ?? (files.truncated ? `listing bound of ${MAX_MARKER_ENTRIES} entries reached` : 'no content-addressed file')))
362
+ if (!sample) return { ok: false, reason: `${versionDir}/files holds no content-addressed file` }
363
+ return { ok: true }
364
+ }
365
+
366
+ const PROVERS = {
367
+ 'pnpm-install': provePnpmInstall,
368
+ 'uv-venv': proveUvVenv,
369
+ 'next-build': proveNextBuild,
370
+ 'pnpm-store': provePnpmStore,
371
+ }
372
+
373
+ function proveMarkers(format, target) {
374
+ const root = resolve(target)
375
+ let st
376
+ try { st = lstatSync(root) } catch (error) {
377
+ if (error?.code === 'ENOENT') return { ok: false, state: 'missing', reason: `${root} does not exist`, markers: [marker('.', false, 'missing')], root }
378
+ return { ok: false, state: 'partial', reason: `cannot inspect ${root}: ${error?.code ?? error?.message}`, markers: [marker('.', false, 'unreadable')], root }
379
+ }
380
+ if (st.isSymbolicLink()) return { ok: false, state: 'partial', reason: `${root} is a symbolic link; a linked root cannot prove the format`, markers: [marker('.', true, 'symbolic link')], root }
381
+ if (!st.isDirectory()) return { ok: false, state: 'partial', reason: `${root} is not a directory`, markers: [marker('.', true, 'not a directory')], root }
382
+ let rootReal
383
+ try { rootReal = real(root) } catch (error) {
384
+ return { ok: false, state: 'partial', reason: `cannot resolve ${root}: ${error?.code ?? error?.message}`, markers: [marker('.', true, 'unresolvable')], root }
385
+ }
386
+ const listing = listDir(root)
387
+ if (listing.error) return { ok: false, state: 'partial', reason: `cannot enumerate ${root}: ${listing.error}`, markers: [marker('.', true, 'unreadable')], root, rootReal }
388
+ const markers = []
389
+ const result = PROVERS[format](root, markers)
390
+ if (result.ok) return { ok: true, state: 'ready', reason: null, markers, root, rootReal, interpreter: result.interpreter ?? null }
391
+ // A root that still holds something is a partial root: it looks present after a manual
392
+ // cleanup but nothing here proves it usable. An empty root with no marker is missing.
393
+ const present = markers.some((entry) => entry.present)
394
+ const state = present || listing.entries.length > 0 ? 'partial' : 'missing'
395
+ return { ok: false, state, reason: result.reason, markers, root, rootReal }
396
+ }
397
+
398
+ // ---- rebuild input proof ----
399
+
400
+ function flattenInputs(inputs) {
401
+ if (Array.isArray(inputs)) return inputs
402
+ if (isObject(inputs)) return Object.values(inputs)
403
+ return null
404
+ }
405
+
406
+ function proveInput({ name, path, role, parseJson, root }) {
407
+ let st
408
+ try { st = lstatSync(path) } catch (error) {
409
+ if (error?.code === 'ENOENT') return { error: `rebuild input ${path} is missing` }
410
+ return { error: `cannot inspect rebuild input ${path}: ${error?.code ?? error?.message}` }
411
+ }
412
+ if (st.isSymbolicLink()) return { error: `rebuild input ${path} is a symbolic link; a link is not a proven source` }
413
+ if (!st.isFile()) return { error: `rebuild input ${path} is not a regular file` }
414
+ if (st.nlink !== 1) return { error: `rebuild input ${path} has ${st.nlink} hard links; the file on disk is ambiguous` }
415
+ if (st.size <= 0) return { error: `rebuild input ${path} is empty` }
416
+ if (st.size > MAX_REBUILD_INPUT_BYTES) return { error: `rebuild input ${path} exceeds the ${MAX_REBUILD_INPUT_BYTES}-byte bound` }
417
+ let realPath
418
+ try { realPath = real(path) } catch (error) {
419
+ return { error: `cannot resolve rebuild input ${path}: ${error?.code ?? error?.message}` }
420
+ }
421
+ if (containsPath(root, realPath) || normPath(realPath) === normPath(root)) {
422
+ return { error: `rebuild input ${path} resolves inside the resource target; a rebuild input must sit outside it` }
423
+ }
424
+ const read = readBounded(path, st, MAX_REBUILD_INPUT_BYTES)
425
+ if (read.error) return { error: read.error }
426
+ let parsed = false
427
+ if (parseJson) {
428
+ let value
429
+ try { value = JSON.parse(read.buffer.toString('utf8')) } catch { return { error: `rebuild input ${path} is not readable JSON` } }
430
+ if (!isObject(value)) return { error: `rebuild input ${path} does not contain a JSON object` }
431
+ parsed = true
432
+ }
433
+ return {
434
+ value: {
435
+ name,
436
+ path,
437
+ realPath,
438
+ role,
439
+ sourceOwned: true,
440
+ identity: read.identity,
441
+ hash: read.hash,
442
+ bytes: read.bytes,
443
+ parsedJson: parsed,
444
+ },
445
+ }
446
+ }
447
+
448
+ function proveRebuildInputs(format, root, inputs) {
449
+ const spec = FORMATS[format]
450
+ const given = flattenInputs(inputs)
451
+ if (!given) return { ok: false, reason: `explicit canonical rebuild inputs are required to prove ${format}`, proofs: [] }
452
+ if (given.length > MAX_REBUILD_INPUTS) return { ok: false, reason: `${format} accepts at most ${MAX_REBUILD_INPUTS} rebuild inputs`, proofs: [] }
453
+ const byName = new Map()
454
+ for (const value of given) {
455
+ if (!nonEmpty(value) || !isAbsolute(value)) return { ok: false, reason: `rebuild input ${JSON.stringify(value)} must be an absolute path`, proofs: [] }
456
+ const name = basename(value)
457
+ if (!spec.inputs.includes(name)) return { ok: false, reason: `${name} is not a canonical rebuild input for ${format} (expected ${spec.inputs.join(' + ')})`, proofs: [] }
458
+ if (byName.has(name)) return { ok: false, reason: `rebuild input ${name} was supplied more than once`, proofs: [] }
459
+ byName.set(name, resolve(value))
460
+ }
461
+ const missing = spec.inputs.filter((name) => !byName.has(name))
462
+ if (missing.length) return { ok: false, reason: `missing canonical rebuild input(s) for ${format}: ${missing.join(', ')}`, proofs: [] }
463
+ const proofs = []
464
+ for (const name of spec.inputs) {
465
+ const path = byName.get(name)
466
+ const proof = proveInput({ name, path, root, role: name.includes('lock') ? 'lockfile' : 'manifest', parseJson: spec.jsonInputs.includes(name) })
467
+ if (proof.error) return { ok: false, reason: proof.error, proofs: [] }
468
+ proofs.push(proof.value)
469
+ }
470
+ return { ok: true, reason: null, proofs }
471
+ }
472
+
473
+ // ---- public API ----
474
+
475
+ export function classifyEmbeddedNames({ format, names } = {}) {
476
+ const resolved = formatId(format)
477
+ const typed = resolved !== 'generic'
478
+ const allowed = []
479
+ const blocking = []
480
+ for (const name of Array.isArray(names) ? names : []) {
481
+ if (!nonEmpty(name)) continue
482
+ const base = basename(name)
483
+ if (typed && VENDORED_SOURCE_NAMES.has(base)) { allowed.push(name); continue }
484
+ // A vendored name blocks for a generic resource: only a proven typed format may
485
+ // embed one, so a folder name never grants itself authority.
486
+ if (RUN_METADATA_NAMES.has(base) || VENDORED_SOURCE_NAMES.has(base)) blocking.push(name)
487
+ }
488
+ return { format: resolved, allowEmbeddedSourceNames: typed, allowed, blocking }
489
+ }
490
+
491
+ export function validateRebuildInputs(inputProofs) {
492
+ if (!Array.isArray(inputProofs) || inputProofs.length === 0) {
493
+ return { ok: false, reason: 'no rebuild input proof was recorded, so the artifact cannot be proven reproducible', checked: 0, changed: [] }
494
+ }
495
+ if (inputProofs.length > MAX_REBUILD_INPUTS) {
496
+ return { ok: false, reason: `more than ${MAX_REBUILD_INPUTS} rebuild input proofs were recorded`, checked: 0, changed: [] }
497
+ }
498
+ const changed = []
499
+ for (const proof of inputProofs) {
500
+ const name = isObject(proof) && nonEmpty(proof.name) ? proof.name : '(unnamed)'
501
+ const path = isObject(proof) && nonEmpty(proof.path) ? proof.path : null
502
+ const record = (kind, detail) => changed.push({ name, path, kind, detail })
503
+ if (!isObject(proof) || !nonEmpty(proof.path) || !nonEmpty(proof.hash) || !isObject(proof.identity)) {
504
+ record('unproven', 'the recorded proof is incomplete')
505
+ continue
506
+ }
507
+ let st
508
+ try { st = lstatSync(proof.path) } catch (error) {
509
+ record(error?.code === 'ENOENT' ? 'missing' : 'unreadable', error?.code ?? error?.message ?? 'unreadable')
510
+ continue
511
+ }
512
+ if (st.isSymbolicLink()) { record('path', 'the input is now a symbolic link'); continue }
513
+ if (!st.isFile()) { record('path', 'the input is no longer a regular file'); continue }
514
+ if (st.nlink !== 1) { record('hardlink', `the input now has ${st.nlink} hard links`); continue }
515
+ let realPath
516
+ try { realPath = real(proof.path) } catch (error) {
517
+ record('unreadable', `cannot resolve the input: ${error?.code ?? error?.message}`)
518
+ continue
519
+ }
520
+ if (nonEmpty(proof.realPath) && normPath(realPath) !== normPath(proof.realPath)) {
521
+ record('path', `the input now resolves to ${realPath}`)
522
+ continue
523
+ }
524
+ if (st.dev !== proof.identity.dev || st.ino !== proof.identity.ino) {
525
+ record('identity', 'the input was replaced by a different file')
526
+ continue
527
+ }
528
+ if (st.size !== proof.identity.size) { record('content', `the input size changed from ${proof.identity.size} to ${st.size}`); continue }
529
+ if (st.size > MAX_REBUILD_INPUT_BYTES) { record('content', `the input exceeds the ${MAX_REBUILD_INPUT_BYTES}-byte bound`); continue }
530
+ const read = readBounded(proof.path, st, MAX_REBUILD_INPUT_BYTES)
531
+ if (read.error) { record('unreadable', read.error); continue }
532
+ if (read.hash !== proof.hash) record('content', 'the input content changed')
533
+ }
534
+ if (changed.length) {
535
+ return { ok: false, reason: `${changed.length} rebuild input(s) no longer match the recorded proof: ${changed.map((entry) => `${entry.name} (${entry.kind})`).join(', ')}`, checked: inputProofs.length, changed }
536
+ }
537
+ return { ok: true, reason: null, checked: inputProofs.length, changed: [] }
538
+ }
539
+
540
+ function readReferenceSymlinks(inspection) {
541
+ const list = Array.isArray(inspection?.externalSymlinks) ? inspection.externalSymlinks : []
542
+ return list
543
+ .filter((entry) => isObject(entry) && nonEmpty(entry.path))
544
+ .map((entry) => ({ path: entry.path, target: nonEmpty(entry.target) ? entry.target : null }))
545
+ }
546
+
547
+ // The caller's own tree inspection decides the name verdict, so a typed format can only
548
+ // relax the guard when that inspection finished and found no run metadata.
549
+ function inspectionBlock(format, inspection) {
550
+ const names = classifyEmbeddedNames({ format, names: inspection?.metadataNames })
551
+ if (inspection && inspection.ok !== true) {
552
+ return { blocked: true, names, reason: `the containing tree inspection did not finish (${inspection.reason ?? 'no reason given'}), so the format cannot be proven complete` }
553
+ }
554
+ if (names.blocking.length) {
555
+ return { blocked: true, names, reason: `run metadata inside the target is never disposable and a typed format does not change that: ${names.blocking.slice(0, 3).join(', ')}` }
556
+ }
557
+ return { blocked: false, names, reason: null }
558
+ }
559
+
560
+ export function inspectResourceFormat(options = {}) {
561
+ const requested = options?.format
562
+ const format = formatId(requested)
563
+ if (format === 'generic') return verdict({ ok: false, state: 'unproven', reason: genericReason(requested) })
564
+ if (!nonEmpty(options?.path)) return verdict({ ok: false, state: 'unproven', format, reason: `${format} needs the resource path it must prove` })
565
+
566
+ const markerProof = proveMarkers(format, options.path)
567
+ const inspection = inspectionBlock(format, options?.inspection)
568
+ const referenceSymlinks = readReferenceSymlinks(options?.inspection)
569
+ const base = {
570
+ format,
571
+ root: markerProof.rootReal ?? markerProof.root,
572
+ markers: markerProof.markers,
573
+ embeddedSourceNames: inspection.names.allowed,
574
+ blockingMetadata: inspection.names.blocking,
575
+ referenceSymlinks,
576
+ }
577
+
578
+ if (!markerProof.ok) return verdict({ ...base, ok: false, state: markerProof.state, reason: markerProof.reason })
579
+ if (inspection.blocked) return verdict({ ...base, ok: false, state: 'partial', reason: inspection.reason })
580
+ const inputs = proveRebuildInputs(format, markerProof.rootReal ?? markerProof.root, options.inputs)
581
+ if (!inputs.ok) {
582
+ return verdict({ ...base, ok: false, state: 'inputs-changed', reason: inputs.reason, inputProofs: null })
583
+ }
584
+ return verdict({
585
+ ...base,
586
+ ok: true,
587
+ state: 'ready',
588
+ allowEmbeddedSourceNames: true,
589
+ allowReferenceSymlinks: true,
590
+ inputProofs: inputs.proofs,
591
+ })
592
+ }
593
+
594
+ export function classifyRebuildState(options = {}) {
595
+ const requested = options?.format
596
+ const format = formatId(requested)
597
+ if (format === 'generic') return { ok: false, state: 'unproven', reason: genericReason(requested), format: 'generic', root: null, markers: [], inputCheck: null }
598
+ if (!nonEmpty(options?.path)) return { ok: false, state: 'unproven', reason: `${format} needs the resource path it must classify`, format, root: null, markers: [], inputCheck: null }
599
+
600
+ const markerProof = proveMarkers(format, options.path)
601
+ const inspection = inspectionBlock(format, options?.inspection)
602
+ const result = { format, root: markerProof.rootReal ?? markerProof.root, markers: markerProof.markers, blockingMetadata: inspection.names.blocking, inputCheck: null }
603
+ if (!markerProof.ok) return { ok: false, state: markerProof.state, reason: markerProof.reason, ...result }
604
+ if (inspection.blocked) return { ok: false, state: 'partial', reason: inspection.reason, ...result }
605
+
606
+ const required = FORMATS[format].inputs
607
+ const recorded = Array.isArray(options.inputProofs) ? options.inputProofs : []
608
+ const names = recorded.map((proof) => (isObject(proof) && nonEmpty(proof.name) ? proof.name : null))
609
+ const namesMatch = names.length === required.length && required.every((name) => names.includes(name))
610
+ if (!namesMatch) {
611
+ return {
612
+ ok: false,
613
+ state: 'inputs-changed',
614
+ reason: `the recorded rebuild input proof does not match the canonical ${format} inputs (expected ${required.join(' + ')})`,
615
+ ...result,
616
+ }
617
+ }
618
+ // Persisted labels are not authority: recheck each actual path's canonical
619
+ // input name and its location outside the resource before trusting its hash.
620
+ const fresh = proveRebuildInputs(format, result.root, recorded.map((proof) => proof?.path))
621
+ if (!fresh.ok) return { ok: false, state: 'inputs-changed', reason: fresh.reason, ...result }
622
+ const check = validateRebuildInputs(recorded)
623
+ if (!check.ok) return { ok: false, state: 'inputs-changed', reason: check.reason, ...result, inputCheck: check }
624
+ return { ok: true, state: 'ready', reason: null, ...result, inputCheck: check }
625
+ }