diff --git a/cli/plugin-kit-ai/internal/authoring/scaffold/template_quoting_test.go b/cli/plugin-kit-ai/internal/authoring/scaffold/template_quoting_test.go index 4aaa9677..c73dc10d 100644 --- a/cli/plugin-kit-ai/internal/authoring/scaffold/template_quoting_test.go +++ b/cli/plugin-kit-ai/internal/authoring/scaffold/template_quoting_test.go @@ -100,7 +100,9 @@ func checkTemplateJavaScriptSyntax(t *testing.T, source []byte) { if err := os.WriteFile(path, source, 0600); err != nil { t.Fatal(err) } - ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) + // Allow bounded headroom for slow CI process startup, including Windows. + const timeout = 60 * time.Second + ctx, cancel := context.WithTimeout(context.Background(), timeout) defer cancel() // Parse only a newly generated temporary fixture. Do not import or execute // the module, load the SDK, install dependencies, or inherit Node preload flags. @@ -111,7 +113,13 @@ func checkTemplateJavaScriptSyntax(t *testing.T, source []byte) { volume := filepath.VolumeName(dir) cmd.Env = append(cmd.Env, "SystemRoot="+os.Getenv("SystemRoot"), "HOMEDRIVE="+volume, "HOMEPATH="+strings.TrimPrefix(dir, volume)) } - if out, err := cmd.CombinedOutput(); err != nil { - t.Fatalf("node --check: %v\n%s", err, out) + // Bound output-pipe waits as well as process execution. OS process startup + // itself may delay cancellation, so this is not a hard wall-clock guarantee. + cmd.WaitDelay = 5 * time.Second + started := time.Now() + out, err := cmd.CombinedOutput() + ctxErr := ctx.Err() + if err != nil || ctxErr != nil { + t.Fatalf("node --check: elapsed=%s timeout=%s context=%v process=%v output=%q", time.Since(started), timeout, ctxErr, err, out) } } diff --git a/npm/agentplugins/scripts/packed-installer-bridge.md b/npm/agentplugins/scripts/packed-installer-bridge.md index 38e2d0e3..cfbfd2a9 100644 --- a/npm/agentplugins/scripts/packed-installer-bridge.md +++ b/npm/agentplugins/scripts/packed-installer-bridge.md @@ -406,3 +406,116 @@ are still missing. Synthetic unit fixtures mock that later capability and child execution only; harmless tool bytes are never executed and establish no authentic acceptance. Full producer/validators/facades, workflow, J/E/P, genuine matrix and remaining release gates remain mandatory next-lane work. + +### C3b delivery step 2: fixed scenarios and producer + +This checkpoint implements `scenarioContract`, `verifyNpmLifecycle`, +`verifyCacheProcess`, `verifyResults` and `produceJourney` in the authentic +reader module. The original 55 kit / 127 pair core rows, including all eighteen +installer rows per pair, remain unchanged. A separate immutable inventory adds +five kit or 34 pair npm lifecycle rows, cache acquisition/repair/invalid-locator +rows, simultaneous requests, literal arguments and host cancellation scenarios. +Core command indices link observed processes to `commands.json`; they do not +replace its inventory. No caller supplies commands, validators or policy. + +The producer uses the accepted source-frozen tools API and closed +`authoring-public-produce/v1` request. It authenticates I/S before installing, +compares both original packs and all native subject pins, derives fresh disjoint +roots, invokes actual npm and its installed shims, finalizes the supported +observer, rechecks source/tools/custody and then writes J plus local admission. +Kit postinstall must use the selected npm Node. POSIX invokes the installed +shim directly; Windows uses fixed `.cmd` version invocations and PowerShell +`.ps1` calls with separately quoted literal arguments. npm install retains +`--offline --ignore-scripts=false --foreground-scripts --no-audit --no-fund`; +uninstall retains the corresponding fixed flags and exact package name. + +The three new records are closed, ordered schema/cell/row tables. Process rows +bind ID/core index, actual argv/cwd/environment, executable/runtime identities, +stdout/stderr size and SHA256, exit/signal, before/after project/prefix/cache/ +client/state/input identities, observed boundaries/counts, postinstall and +monotonic intervals and the pinned literal-argument manifest effect. Boundary +events are chronological: reached native/waiter boundary before cancellation, +then final reaping; a cancelled waiter never launches native code. Repair +records the exact intentional corruption before checked recovery. Cache +evidence also binds descendant finalization; +installer evidence binds assessment and eighteen readbacks. Raw observations +and assessment are pinned `sidecars/` files, checked exhaustively with the +128 MiB aggregate ceiling. The three scenario record files carry +`rows: {shards: [{path,size,sha256}]}` with fixed ordered +`sidecars/-rows-.json` transcript names. Each process row +and envelope stays within 1 MiB; each ordered transcript shard stays within +16 MiB. Readers verify pins, canonical partitioning and the aggregate before +accepting expanded rows. Long workspace paths do not require omitting rows. +Core transcripts retain 16 MiB and each output retains 1 MiB limits. Overflow +prevents completion. Equality failures report a bounded assertion label rather +than constructing potentially enormous diagnostic object diffs. + +Result validation checks engine/product versions, embedded schema and profile +pins, command and client inventories, read profile, independent policy states, +exact components and names, mutation effects, manifest identities and pair JSON +and tree equality. Only explicit product/version and displayed invocation +prefix differences are normalized. Directory and file modes and empty +directories remain evidence. Cache checks require real repair, warm zero new +acquisition/commit/download, four overlapping cold requests plus two warm, +peer namespace preservation and reached cancellation/waiter boundaries. + +**Execution prerequisites remain absent on this source.** The fixed modules +must be supplied and reviewed by their existing owners: + +- `public-authoring-custody.js`: `readPublicInputs` returning authenticated + `{stage,input}` with original retained subjects and checked pack closure. +- `public-process-observation.js`: `openPublicObservation` and + `verifyPublicObservation`. Its session supplies `run`, `cancel`, `finish`. +- `public-installer-evidence.js`: `requirePublicInstaller` and + `verifyPublicInstaller`, covering actual clean Codex detection, all three + lifecycle sources, genuine assessment/services and state/client readbacks. + +The process port's concrete return protocol for this consumer is +`run -> {row,stdout,stderr}` and +`finish -> {finalization,assessment,readbacks}`. The observer owns pinned raw +sidecars in the supplied evidence root. `cancel({id,event})` uses the fixed +scenario's reached boundary; it cannot silently pass an unreached signal. +`verifyPublicObservation` returns checked `{rows,finalization}`; +`verifyPublicInstaller` returns checked `{assessment,readbacks}`. Boolean +success is rejected. These are required integration contracts, not supplied +observer/security implementations or authentic execution evidence. + +Missing exports fail before npm/native effects with their exact module/export +names. Post-admission setup, process, cancellation and finalization failures +retain bounded diagnostics, including nested primary causes, without writing J. +Synthetic unit fixtures establish semantic and orchestration controls +only. The same-live-root bridge receives the producer's original ten projects; +it must still run and independently verify its ten leaves/thirty plans in the +later authorized integrated invocation. No Go gate is executed by this writer. +Completed remote J/E, aggregation, E2, workflow and P remain mandatory step 3; +`readAcceptance` stays closed. Full phases 0–11, genuine E2E, native/N2, release +qualification and distribution gates remain open. This patch awaits independent +review and does not constitute authentic public acceptance. + +### R1/R2 bounded source correction (2026-09-10) + +The authentic public validator now requires the exact five fixed `publicInit` +closures: original and extra Skills, manifest, README, .gitignore, remote MCP +references and the complete generated Node stdio sources/package/lock bytes. +File and directory inventories and host modes are checked independently for each +product. Captured file sizes/hashes bind the expected bytes before ordinary +`agentplugins-tree-sha256-v1` framing is recomputed and compared with every +retained read identity. The root is omitted from that engine digest; directory +entries are included. The snapshot still seals the root and all empty directories. +No additional empty directory is generated by these fixed commands. Existing +live snapshot, bridge, evidence bounds and command inventories remain mandatory. + +Successful observer acquisition enters finalization scope before session method +validation. An available finish is called exactly once even when run/cancel is +missing; validation and finalizer errors are retained together, with no J. +The observation owner must release all resources if open throws or returns no +usable finalizer. The caller cannot finalize an absent method; its failure receipt +is not evidence of quiescence. Only synthetic owner-interface tests cover this +correction; no real observer experiment or substitute observer was performed. + +This correction does not authenticate execution or accept E2E/release. Genuine +facades/provision, step 3 and full phases 0–11 remain open. N2 cyber refusal is +NOT ACCEPTED. Refused security/crypto/ZIP work must not be retried, rerouted or +replaced. Quarantined Windows parent-sharing/concurrency reproducers, ptrace or +alternate observers, denied localhost/private-network/raw-download probes, +network/auth/download/native execution and provisioning remain excluded. diff --git a/npm/agentplugins/scripts/public-authoring-acceptance.js b/npm/agentplugins/scripts/public-authoring-acceptance.js index 84da463b..bf642979 100644 --- a/npm/agentplugins/scripts/public-authoring-acceptance.js +++ b/npm/agentplugins/scripts/public-authoring-acceptance.js @@ -1,7 +1,7 @@ "use strict"; -// C3a: closed local input custody and structural J contract. No producer, E -// authentication, installer policy, archive engine or execution override lives here. +// C3 local custody, fixed producer and semantic evidence contracts. Completed E +// admission belongs to step 3; installer/observation/custody engines stay external. const fs = require("node:fs"); const path = require("node:path"); const assert = require("node:assert/strict"); @@ -19,11 +19,13 @@ const J_FIELDS = ["schema", "status", "identity", "authoring_mode", "asset_scope const LANES = Object.freeze(["skill", "mcp-remote", "mcp-stdio", "hybrid-remote", "hybrid-stdio"]); const EVIDENCE = Object.freeze(["commands.json", "projects.json", "npm-lifecycle.json", "cache-process.json", "installer.json"]); const ASSERTIONS = Object.freeze(["fixed_commands", "pair_parity", "projects_preserved", "npm_lifecycle", "cache_process", "production_installer", "children_reaped"]); -const MISSING = "C3b required: reviewed public installer result validator and whole-descendant observer; full npm/cache/process/parity finalization is not implemented; J execution admission is closed"; +const MISSING = "C3b required: PUBLIC_FACADE_REQUIRED:public-authoring-custody.js#readPublicInputs,public-process-observation.js#openPublicObservation,public-process-observation.js#verifyPublicObservation,public-installer-evidence.js#requirePublicInstaller,public-installer-evidence.js#verifyPublicInstaller"; const matrix = Object.freeze(["linux-amd64", "linux-arm64", "darwin-amd64", "darwin-arm64", "windows-amd64", "windows-arm64"].flatMap(target => [18, 22, 24].map(node => Object.freeze({ key: `${target}/${node === 18 ? "kit" : "pair"}-node${node}`, target, node, products: Object.freeze(node === 18 ? ["plugin-kit-ai"] : [...c.PRODUCTS]) })))); -const agree = (a, b, label) => assert.deepEqual(a, b, `C3 ${label}`); +// Compare without asking node:assert to render an unbounded object/Buffer diff. +// Rejected transcript-sized values must remain cheap to report under memory limits. +const agree = (a, b, label) => assert.ok(require('node:util').isDeepStrictEqual(a, b), `C3 ${label}`); function cell(key) { const found = matrix.find(row => row.key === key); assert.ok(found, "fixed C3 cell required"); return found; } function absolute(value) { assert.ok(typeof value === "string" && value.length <= 4096 && !/[\x00-\x1f\x7f]/.test(value) && @@ -75,7 +77,7 @@ function tools(value, selected) { for (const key of ["orchestrator_node", "npm_node", "shim_node", "npm", "go"]) { const t = value[key]; if (key === "go" && selected.key !== "linux-amd64/pair-node22") { agree(t, null, "Go only in bridge cell"); continue; } - fields(t, ["path", "sha256", "version"], "C3 tool pin"); absolute(t.path); hash(t.sha256, key); + fields(t, ["path", "sha256", "version"], "C3 tool pin"); hostAbsolute(t.path, selected.key); hash(t.sha256, key); assert.ok(typeof t.version === "string" && t.version.length < 128 && /^[\x21-\x7e]+$/.test(t.version), "bounded tool version"); if (key.endsWith("_node")) assert.match(t.version, /^v[1-9][0-9]*\.[0-9]+\.[0-9]+$/); if (["npm_node", "shim_node"].includes(key)) assert.ok(t.version.startsWith(`v${selected.node}.`), "actual selected Node major"); @@ -132,7 +134,7 @@ function journey(value, inputBytes, stageBytes) { agree(value.command_contract_sha256, c.digest(c.encode(commandContract(value.cell))), "fixed core command contract"); agree(value.subjects, Object.fromEntries(c.PRODUCTS.map(p => [p, input.products[p].assets])), "all twelve outer/inner native subject pins"); fields(value.projects, selected.products, "C3 projects"); - for (const p of selected.products) absolute(value.projects[p]); + for (const p of selected.products) hostAbsolute(value.projects[p], selected.key); assert.ok(Array.isArray(value.evidence) && value.evidence.length === EVIDENCE.length, "fixed evidence table"); agree(Reflect.ownKeys(value.evidence), [...EVIDENCE.map((_, i) => String(i)), "length"], "plain evidence array fields"); value.evidence.forEach((row, i) => { @@ -235,10 +237,10 @@ function readJourneyInputs(value) { projects.push({ product, lane, source }); } } - const evidence = {}; + const evidence = {}, budget = { size: j.evidence.reduce((n, row) => n + row.size, 0) }; for (const row of j.evidence) { const bytes = pin(path.join(a.journey_root, row.path), row.sha256, row.path === "commands.json" ? TRANSCRIPT_LIMIT : LIMIT); - agree(bytes.length, row.size, "evidence size"); evidence[row.path] = bounded(bytes, row.path === "commands.json" ? TRANSCRIPT_LIMIT : LIMIT); + agree(bytes.length, row.size, "evidence size"); evidence[row.path] = expandEvidence(row.path, bounded(bytes, row.path === "commands.json" ? TRANSCRIPT_LIMIT : LIMIT), a.journey_root, budget); } const snapshots = [a.stage_root, a.input_root, a.journey_root, a.fixture_root].map(root => bridge.snapshot(root)); agree(evidence["projects.json"], Object.fromEntries(cell(j.cell).products.map(p => [p, bridge.snapshot(j.projects[p])])), "original project trees and modes"); @@ -248,45 +250,952 @@ function readJourneyInputs(value) { return { record: j, evidence, identity: j.identity, repo: a.repo, candidate_sha256: j.candidate_sha256, packs: j.packs, projects, snapshots, protected_paths: [a.work_parent, r.admission, ...toolPins.map(t => t.path)] }; } -function verifyJourney(local) { - const { record: j, evidence } = local, expected = commandContract(j.cell), rows = evidence["commands.json"]; - assert.ok(Array.isArray(rows) && rows.length === expected.length, "exact ordered C3 core command rows"); +// C3b step 2. These pure contracts are replayed against authenticated observations; +// neither a caller assertion nor a synthetic unit fixture authenticates execution. +const AGGREGATE_LIMIT = 128 * LIMIT; +const PRODUCE_FIELDS = ['schema', 'selected', 'workflow_sha', 'stage', 'input_file', 'repo', 'work_parent', 'output', 'cell', 'tools', 'producer']; +const FACADES = Object.freeze({ + 'public-authoring-custody': ['readPublicInputs'], + 'public-process-observation': ['openPublicObservation', 'verifyPublicObservation'], + 'public-installer-evidence': ['requirePublicInstaller', 'verifyPublicInstaller'] +}); +function requireFacades(key) { + const result = {}, missing = []; + for (const [name, exports] of Object.entries(FACADES)) { + if (name === 'public-installer-evidence' && cell(key).node === 18) continue; + const file = path.join(__dirname, name + '.js'); + if (!fs.existsSync(file)) { missing.push(...exports.map(e => `${name}.js#${e}`)); continue; } + const api = require(file); + for (const e of exports) if (typeof api[e] !== 'function') missing.push(`${name}.js#${e}`); + result[name] = api; + } + assert.equal(missing.length, 0, `C3b required: PUBLIC_FACADE_REQUIRED:${missing.join(',')}`); + return result; +} +const hostPath = key => key.startsWith('windows-') ? path.win32 : path.posix; +function hostAbsolute(value, key) { + const p = hostPath(key); + assert.ok(typeof value === 'string' && value.length <= 4096 && !/[\x00-\x1f\x7f]/.test(value) && p.isAbsolute(value) && + p.normalize(value) === value && value !== p.parse(value).root && !value.startsWith('\\\\'), 'canonical recorded host path'); + return value; +} +function freeze(value) { if (value && typeof value === 'object') { Object.values(value).forEach(freeze); Object.freeze(value); } return value; } +function list(value, count, label) { + assert.ok(Array.isArray(value) && value.length === count, label); + agree(Reflect.ownKeys(value), [...value.map((_, i) => String(i)), 'length'], `${label} plain array`); +} +function nonnegative(value, maximum, label) { assert.ok(Number.isSafeInteger(value) && value >= 0 && value <= maximum, label); } +function textValue(value, maximum = LIMIT) { assert.ok(typeof value === 'string' && Buffer.byteLength(value) <= maximum && !value.includes('\0'), 'bounded text'); } +function digestID(value) { assert.match(value, /^sha256:[0-9a-f]{64}$/); hash(value.slice(7), 'digest identity'); } +function sidecar(value) { + fields(value, ['path', 'size', 'sha256'], 'observation sidecar'); + assert.match(value.path, /^sidecars\/[a-z0-9][a-z0-9._-]{0,150}$/); + positive(value.size, TRANSCRIPT_LIMIT, 'sidecar size'); hash(value.sha256, 'sidecar'); +} +/** Supplementary rows have a separate identity/count; core55/core127 never change. */ +function scenarioContract(key) { + const selected = cell(key), npm = [], cache = [], installer = []; + const add = (into, product, kind, prefix, cacheName, suffix = kind, group = null, command = null, event = null) => + into.push({ id: `${product}/${prefix}/${suffix}`, product, kind, prefix, cache: cacheName, group, command, event }); + for (const p of selected.products) { + const prefix = `alone-${p}`; + for (const k of ['install', 'probe', 'uninstall', 'reinstall', 'probe-reinstalled']) add(npm, p, k, prefix, prefix); + } + if (selected.products.length === 2) for (const order of [selected.products, [...selected.products].reverse()]) { + const prefix = `shared-${order[0]}`; + for (const p of order) { add(npm, p, 'install', prefix, prefix); add(npm, p, 'probe', prefix, prefix); } + for (const p of order) { + const peer = order.find(x => x !== p); + add(npm, p, 'uninstall', prefix, prefix); + add(npm, peer, 'probe-peer', prefix, prefix, `peer-after-${p}`); + add(npm, p, 'reinstall', prefix, prefix); + add(npm, p, 'probe-reinstalled', prefix, prefix); + } + } + for (const p of selected.products) { + const prefix = `alone-${p}`; + for (const k of ['cold', 'warm', 'repair', 'invalid-cold', 'invalid-warm']) + add(cache, p, k, prefix, k === 'invalid-cold' ? `invalid-${p}` : `serial-${p}`); + for (let i = 0; i < 4; i++) add(cache, p, 'concurrent-cold', prefix, `concurrent-${p}`, `concurrent-cold-${i}`, `cold-${p}`); + for (let i = 0; i < 2; i++) add(cache, p, 'concurrent-warm', prefix, `concurrent-${p}`, `concurrent-warm-${i}`); + add(cache, p, 'literal-argv', prefix, `serial-${p}`); + if (key.startsWith('windows-')) { + add(cache, p, 'cancel', prefix, `serial-${p}`, 'console-cancel', null, null, 'CTRL_C_EVENT'); + add(cache, p, 'cancel', prefix, `serial-${p}`, 'process-cancel', null, null, 'TerminateProcess'); + } else for (const event of ['SIGINT', 'SIGTERM']) add(cache, p, 'cancel', prefix, `serial-${p}`, event, null, null, event); + add(cache, p, 'waiter-owner', prefix, `waiter-${p}`, 'waiter-owner', `waiter-${p}`); + add(cache, p, 'waiter-cancel', prefix, `waiter-${p}`, 'waiter-cancel', `waiter-${p}`, null, + key.startsWith('windows-') ? 'CTRL_C_EVENT' : 'SIGINT'); + } + if (selected.products.length === 2) for (const p of selected.products) + add(cache, p, 'peer-overlap', `alone-${p}`, 'peer-overlap', 'peer-overlap', 'peer-overlap'); + commandContract(key).forEach((r, i) => add(r.id.startsWith('installer/') ? installer : cache, r.product, 'core', + `alone-${r.product}`, `alone-${r.product}`, `core-${r.id}`, null, i)); + return freeze({ npm, cache, installer }); +} +function rootsFor(output, key) { + const p = hostPath(key); hostAbsolute(output, key); + return Object.fromEntries(['input', 'stage', 'admission', 'projects', 'npm', 'cache', 'client', 'state', 'evidence', 'scenarios'] + .map(n => [n, p.join(output, n)])); +} +function scopePaths(j, scenario, roots) { + const p = hostPath(j.cell), name = scenario.cache; + return { prefix: p.join(roots.npm, scenario.prefix, 'prefix'), home: p.join(roots.cache, name), + cwd: scenario.kind === 'core' ? commandCwd(j, commandContract(j.cell)[scenario.command]) : + p.join(roots.scenarios, scenario.product, scenario.kind === 'literal-argv' ? "cwd spaces ü 'quotes' $literal ; &" : scenario.prefix), + userconfig: p.join(roots.npm, scenario.prefix, 'user.npmrc'), globalconfig: p.join(roots.npm, scenario.prefix, 'global.npmrc'), + npmCache: p.join(roots.npm, scenario.prefix, 'npm-cache') }; +} +function commandCwd(j, want) { + const p = hostPath(j.cell), parent = j.projects[want.product]; + return want.scenario === 'projects' ? parent : p.join(p.dirname(parent), `${want.product} malformed-skill ü`); +} +const LITERAL_DESCRIPTION = 'Use spaces ü "double" \'single\' $HOME $(literal) `literal` ; & | < > %PATH% !literal!'; +function plannedInvocation(j, scenario, roots) { + const p = hostPath(j.cell), s = scopePaths(j, scenario, roots), windows = j.cell.startsWith('windows-'); + const asset = p.join(roots.input, j.subjects[scenario.product][cell(j.cell).target].file); + const env = { HOME: s.home, TMPDIR: p.join(s.home, 'tmp'), + PATH: p.dirname(j.tools[['install', 'reinstall', 'uninstall'].includes(scenario.kind) ? 'npm_node' : 'shim_node'].path), LANG: 'C.UTF-8', LC_ALL: 'C.UTF-8', + NPM_CONFIG_USERCONFIG: s.userconfig, NPM_CONFIG_GLOBALCONFIG: s.globalconfig, NPM_CONFIG_CACHE: s.npmCache, + NPM_CONFIG_OFFLINE: 'true', NPM_CONFIG_AUDIT: 'false', NPM_CONFIG_FUND: 'false', + UAP_PUBLIC_AUTHORING_ASSET_FILE: scenario.kind.startsWith('invalid-') ? p.join(roots.input, 'absent-invalid-locator') : asset, + CODEX_HOME: p.join(roots.client, 'codex'), XDG_CONFIG_HOME: roots.client, XDG_STATE_HOME: roots.state, + XDG_DATA_HOME: p.join(roots.state, 'data'), XDG_CACHE_HOME: p.join(s.home, '.cache') }; + if (windows) { + // Frozen OS destinations, never inherited COMSPEC/PowerShell or caller shell. + env.USERPROFILE = s.home; env.TEMP = env.TMP = p.join(s.home, 'tmp'); + env.SystemRoot = 'C:\\Windows'; env.ComSpec = 'C:\\Windows\\System32\\cmd.exe'; + env.PATHEXT = '.COM;.EXE;.BAT;.CMD'; env.LOCALAPPDATA = p.join(s.home, 'AppData', 'Local'); + } + let argv; + if (['install', 'reinstall', 'uninstall'].includes(scenario.kind)) { + const uninstall = scenario.kind === 'uninstall'; + argv = [j.tools.npm_node.path, j.tools.npm.path, uninstall ? 'uninstall' : 'install', '--global', '--prefix', s.prefix, + '--offline', '--ignore-scripts=false', ...(!uninstall ? ['--foreground-scripts'] : []), '--no-audit', '--no-fund', + uninstall ? contract.PACKAGES[scenario.product] : p.join(roots.stage, j.packs[scenario.product].file)]; + } else { + let args = scenario.kind === 'core' ? [...commandContract(j.cell)[scenario.command].argv] : + scenario.kind === 'literal-argv' ? [...(scenario.product === 'agentplugins' ? ['author'] : []), 'init', 'literal project ü', + '--name', 'literal-project', '--template=skill', '--description', LITERAL_DESCRIPTION, '--format=json'] : ['version', '--format=json']; + if (scenario.kind === 'core') { + const core = commandContract(j.cell)[scenario.command]; + if (core.id.startsWith('installer/') && /\/(dry-run|add)$/.test(core.id)) args[1] = p.join(j.projects[core.product], core.lane); + } + const shim = p.join(s.prefix, ...(windows ? [] : ['bin']), scenario.product); + // PowerShell's call operator with independently single-quoted array elements + // preserves metacharacters. .cmd is exercised separately on core version rows. + if (windows && scenario.kind === 'core' && commandContract(j.cell)[scenario.command].id === 'product-version') { + const quote = v => { assert.ok(!/["%\r\n!]/.test(v), 'cmd fixed version tokens'); return '"' + v + '"'; }; + argv = [env.ComSpec, '/d', '/s', '/c', '"' + [shim + '.cmd', ...args].map(quote).join(' ') + '"']; + } else if (windows) { + const quote = v => "'" + v.replaceAll("'", "''") + "'"; + argv = ['C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe', '-NoLogo', '-NoProfile', '-NonInteractive', + '-Command', '& ' + [shim + '.ps1', ...args].map(quote).join(' ') + '; exit $LASTEXITCODE']; + } else argv = [shim, ...args]; + } + return { id: scenario.id, argv, cwd: s.cwd, env }; +} +function tree(value, key, expectedRoot) { + fields(value, ['root', 'sha256', 'entries'], 'tree snapshot'); hostAbsolute(value.root, key); + if (expectedRoot !== undefined) agree(value.root, expectedRoot, 'snapshot root'); + assert.ok(Array.isArray(value.entries) && value.entries.length > 0 && value.entries.length <= 8192, 'tree entries bound'); + hash(value.sha256, 'tree'); agree(value.sha256, c.digest(c.encode(value.entries)), 'tree digest'); + const seen = new Set(); + for (const e of value.entries) { + fields(e, e.kind === 'directory' ? ['path', 'mode', 'kind'] : ['path', 'mode', 'kind', 'size', 'sha256'], 'project tree entry'); + assert.ok(e.path === '.' || typeof e.path === 'string' && e.path.length <= 4096 && !e.path.startsWith('/') && + !e.path.includes('\\') && !e.path.split('/').some(x => !x || x === '.' || x === '..'), 'relative project entry'); + assert.ok(!seen.has(e.path.toLowerCase()), 'unique tree path'); seen.add(e.path.toLowerCase()); + assert.ok(['file', 'directory'].includes(e.kind), 'project regular file/directory'); + nonnegative(e.mode, 0o777, 'exact host mode'); + if (e.kind === 'file') { nonnegative(e.size, LIMIT, 'file bound'); hash(e.sha256, 'file'); } + assert.ok(!/(^|\/)(hooks|plugin\.yaml)(\/|$)/.test(e.path), 'no generated legacy manifest/root hooks'); + } + agree(value.entries[0].path, '.', 'tree root entry'); + for (const e of value.entries.slice(1)) { + const parent = path.posix.dirname(e.path); assert.ok(value.entries.some(x => x.path === parent && x.kind === 'directory'), 'explicit parent including empty directories'); + } + return value; +} +const CORRUPTION_BYTES = Buffer.from('C3 intentional owned cache corruption\n'); +function binary(value, j, product, corrupted = false) { + if (value === null) return; + fields(value, ['path', 'sha256', 'size', 'mode'], 'observed binary'); hostAbsolute(value.path, j.cell); + const expected = corrupted ? { sha256: c.digest(CORRUPTION_BYTES), size: CORRUPTION_BYTES.length } : j.subjects[product][cell(j.cell).target].binary; + agree(value.sha256, expected.sha256, 'I inner binary bytes'); agree(value.size, expected.size, 'I inner binary size'); + agree(value.mode, j.cell.startsWith('windows-') ? 0o666 : 0o755, 'native executable host mode'); +} +function observedState(value, j, corruptedProduct = null) { + fields(value, ['projects', 'prefix', 'cache', 'client', 'state', 'inputs'], 'observed state'); + for (const k of ['projects', 'client', 'state', 'inputs']) hash(value[k], k); + fields(value.prefix, cell(j.cell).products, 'prefix products'); fields(value.cache, cell(j.cell).products, 'cache products'); + for (const product of cell(j.cell).products) { + const pkg = value.prefix[product]; + if (pkg !== null) { + fields(pkg, ['tree', 'shims'], 'installed package'); hash(pkg.tree, 'package tree'); + const kinds = j.cell.startsWith('windows-') ? ['posix', 'cmd', 'powershell'] : ['posix']; list(pkg.shims, kinds.length, 'actual npm shim inventory'); + pkg.shims.forEach((shim, i) => { + fields(shim, ['kind', 'path', 'sha256', 'mode', 'target'], 'npm shim'); agree(shim.kind, kinds[i], 'shim kind'); + hostAbsolute(shim.path, j.cell); hash(shim.sha256, 'shim'); nonnegative(shim.mode, 0o777, 'shim mode'); + if (j.cell.startsWith('windows-')) { agree(shim.target, null, 'Windows regular shim'); agree(shim.mode, 0o666, 'Windows shim mode'); } + else { textValue(shim.target, 4096); assert.ok(shim.target.endsWith(`/bin/${product}.js`), 'npm link to product bin'); agree(shim.mode, 0o777, 'POSIX npm symlink'); } + }); + } + binary(value.cache[product], j, product, product === corruptedProduct); + } +} +function expectedCachePath(j, roots, scenario, product) { + const p = hostPath(j.cell), native = j.subjects[product][cell(j.cell).target].binary; + return p.join(scopePaths(j, scenario, roots).home, '.cache', 'universal-agent-plugins', 'public-authoring-v2', contract.MODE, + j.identity.commit, j.candidate_sha256, product, j.identity.versions[product], cell(j.cell).target, native.sha256, native.file); +} +function verifyObservedRow(row, scenario, j, roots, commands) { + fields(row, ['id', 'command', 'argv', 'cwd', 'env', 'executable', 'runtime', 'stdout', 'stderr', 'status', 'signal', + 'before', 'after', 'observation', 'events', 'acquisitions', 'commits', 'downloads', 'native_launches', 'postinstall', 'interval', 'literal'], 'observed process row'); + agree(row.id, scenario.id, 'fixed scenario ID'); agree(row.command, scenario.command, 'core reference'); + const want = plannedInvocation(j, scenario, roots); + for (const k of ['argv', 'cwd', 'env']) agree(row[k], want[k], `actual ${k}`); + fields(row.executable, ['path', 'sha256'], 'observed executable'); agree(row.executable.path, want.argv[0], 'actual executable'); hash(row.executable.sha256, 'executable'); + const npm = ['install', 'reinstall', 'uninstall'].includes(scenario.kind); + agree(row.runtime, npm ? j.tools.npm_node : j.tools.shim_node, 'observed runtime, not controller'); + if (npm) agree(row.executable.sha256, j.tools.npm_node.sha256, 'npm executing runtime hash'); + else if (!j.cell.startsWith('windows-')) { + const pkg = row.before.prefix[scenario.product]; assert.ok(pkg, 'installed public shim required'); + agree(row.executable.sha256, pkg.shims[0].sha256, 'observed installed shim bytes'); + } + for (const k of ['stdout', 'stderr']) { fields(row[k], ['size', 'sha256'], 'pinned output'); nonnegative(row[k].size, LIMIT, 'output size'); hash(row[k].sha256, 'output hash'); } + if (scenario.kind === 'literal-argv') { + fields(row.literal, ['root', 'description', 'manifest_sha256'], 'literal argv filesystem effect'); + agree(row.literal.root, hostPath(j.cell).join(want.cwd, 'literal project ü'), 'actual cwd-relative destination'); + agree(row.literal.description, LITERAL_DESCRIPTION, 'literal shell metacharacters preserved'); hash(row.literal.manifest_sha256, 'literal manifest'); + } else agree(row.literal, null, 'no invented literal effect'); + sidecar(row.observation); observedState(row.before, j, scenario.kind === 'repair' ? scenario.product : null); observedState(row.after, j); + for (const state of [row.before, row.after]) for (const product of cell(j.cell).products) { + if (state.cache[product]) agree(state.cache[product].path, expectedCachePath(j, roots, scenario, product), 'exact owned product cache path'); + if (state.prefix[product]) state.prefix[product].shims.forEach(shim => { + const p = hostPath(j.cell), windows = j.cell.startsWith('windows-'); + agree(shim.path, p.join(scopePaths(j, scenario, roots).prefix, ...(windows ? [] : ['bin']), product + ({ posix: '', cmd: '.cmd', powershell: '.ps1' }[shim.kind])), 'exact installed npm shim path'); + }); + } + for (const k of ['acquisitions', 'commits', 'downloads', 'native_launches']) nonnegative(row[k], 32, 'process effect count'); + list(row.interval, 2, 'observed monotonic interval'); row.interval.forEach(x => nonnegative(x, Number.MAX_SAFE_INTEGER, 'monotonic time')); + assert.ok(row.interval[1] >= row.interval[0] && row.interval[1] - row.interval[0] <= 120000, 'fixed 120s scenario deadline'); + assert.ok(Array.isArray(row.events) && row.events.length <= 32, 'bounded observed boundaries'); + row.events.forEach(x => assert.ok(['shim', 'native', 'cache-waiter', 'lock-owner', 'reaped', 'cancel-delivered', 'repair-before-launch', 'locator-rejected'].includes(x), 'fixed boundary')); + agree(new Set(row.events).size, row.events.length, 'unique boundaries'); agree(row.events.at(-1), 'reaped', 'descendants reaped after all observed boundaries'); + if (!npm) { agree(row.events[0], 'shim', 'shim is the first execution boundary'); + if (row.events.includes('native')) assert.ok(row.events.indexOf('shim') < row.events.indexOf('native'), 'shim before native'); + } + agree(row.downloads, 0, 'no native download/fallback'); + for (const k of ['inputs', 'projects', 'client', 'state']) { + const mutation = scenario.kind === 'core' && /\/(init|extra-skill)$/.test(commandContract(j.cell)[scenario.command].id); + const installer = scenario.command !== null && commandContract(j.cell)[scenario.command].id.startsWith('installer/'); + if (!(k === 'projects' && mutation) && !(installer && ['client', 'state'].includes(k))) agree(row.after[k], row.before[k], `preserved ${k}`); + } + const canceled = ['cancel', 'waiter-cancel'].includes(scenario.kind); + const status = scenario.command !== null ? commandContract(j.cell)[scenario.command].status : scenario.kind.startsWith('invalid-') ? 1 : 0; + if (canceled) { + assert.ok(row.events.includes('cancel-delivered') && row.events.includes(scenario.kind === 'waiter-cancel' ? 'cache-waiter' : 'native'), 'reached genuine cancellation boundary'); + const boundary = scenario.kind === 'waiter-cancel' ? 'cache-waiter' : 'native'; + assert.ok(row.events.indexOf(boundary) < row.events.indexOf('cancel-delivered'), 'reached boundary before cancellation delivery'); + agree(row.native_launches, scenario.kind === 'waiter-cancel' ? 0 : 1, 'cancelled waiter never launches native'); + if (scenario.kind === 'waiter-cancel') agree([row.acquisitions, row.commits], [0, 0], 'cancelled waiter never acquires or commits'); + const codes = { SIGINT: 130, SIGTERM: 143, CTRL_C_EVENT: 130, TerminateProcess: 1 }; + agree(row.status, codes[scenario.event], 'fixed cancellation exit mapping'); agree(row.signal, null, 'wrapper maps cancellation exit'); + } else { agree(row.status, status, 'fixed exit'); agree(row.signal, null, 'no unexpected signal'); } + if (!npm && !canceled && !scenario.kind.startsWith('invalid-')) { + assert.ok(row.events.includes('shim') && row.events.includes('native'), 'real shim/native boundaries'); + agree(row.native_launches, 1, 'one native process'); assert.ok(row.after.cache[scenario.product], 'native ran from checked cache'); + if (scenario.kind === 'core') { agree(row.after.cache, row.before.cache, 'core warm cache preserved'); agree([row.acquisitions, row.commits], [0, 0], 'core warm no acquisition/commit'); } + } + if (scenario.command !== null) { + const core = commands[scenario.command]; + for (const [k, value] of Object.entries({ cwd: row.cwd, status: row.status, signal: row.signal })) agree(core[k], value, `core observed ${k}`); + for (const k of ['stdout', 'stderr']) agree(row[k], { size: Buffer.byteLength(core[k]), sha256: c.digest(Buffer.from(core[k])) }, 'core exact observed output pin'); + } + return row; +} +function recordRows(value, schema, j, expected, roots, commands) { + fixed(value.schema, schema, 'scenario schema'); agree(value.cell, j.cell, 'scenario cell'); list(value.rows, expected.length, 'complete ordered scenario rows'); + value.rows.forEach((r, i) => verifyObservedRow(r, expected[i], j, roots, commands)); return value.rows; +} +function verifyNpmLifecycle(j, value, roots, commands = []) { + fields(value, ['schema', 'cell', 'rows'], 'npm lifecycle'); + const expected = scenarioContract(j.cell).npm, rows = recordRows(value, 'authoring-public-npm-lifecycle/v1', j, expected, roots, commands); + const previous = new Map(), originals = new Map(); + rows.forEach((r, i) => { + const s = expected[i], product = s.product, peer = cell(j.cell).products.find(p => p !== product); + if (previous.has(s.prefix)) agree(r.before, previous.get(s.prefix), 'continuous prefix lifecycle history'); + else { agree(Object.values(r.before.prefix), cell(j.cell).products.map(() => null), 'fresh prefix'); agree(Object.values(r.before.cache), cell(j.cell).products.map(() => null), 'cold prefix cache'); } + previous.set(s.prefix, r.after); + if (peer) { agree(r.after.prefix[peer], r.before.prefix[peer], 'peer package/shims/modes unchanged'); agree(r.after.cache[peer], r.before.cache[peer], 'peer cache/binary unchanged'); } + const key = `${s.prefix}/${product}`; + if (['install', 'reinstall'].includes(s.kind)) { + agree(r.before.prefix[product], null, 'install into missing package'); assert.ok(r.after.prefix[product], 'installed package/shims'); + if (s.kind === 'install') originals.set(key, r.after.prefix[product]); else agree(r.after.prefix[product], originals.get(key), 'same tarball reinstall bytes/modes'); + if (product === 'plugin-kit-ai') { + fields(r.postinstall, ['argv', 'runtime', 'acquisitions', 'commits', 'observation'], 'real kit postinstall'); + agree(r.postinstall.argv, [j.tools.npm_node.path, './lib/install.js'], 'genuine npm postinstall command'); + agree(r.postinstall.runtime, j.tools.npm_node, 'selected kit postinstall Node'); sidecar(r.postinstall.observation); + const cold = r.before.cache[product] === null; + agree(r.postinstall.acquisitions, cold ? 1 : 0, 'cold/warm postinstall acquisition'); agree(r.postinstall.commits, cold ? 1 : 0, 'postinstall cache commit'); + assert.ok(r.after.cache[product], 'postinstall checked cache'); + } else agree(r.postinstall, null, 'agent no install script'); + } else { + agree(r.postinstall, null, 'no hidden postinstall'); + if (s.kind === 'uninstall') { assert.ok(r.before.prefix[product], 'installed before removal'); agree(r.after.prefix[product], null, 'removed package and all shims absent'); agree(r.after.cache, r.before.cache, 'uninstall preserves native cache'); } + else { agree(r.after.prefix, r.before.prefix, 'probe read-only package'); assert.ok(r.events.includes('native') && r.native_launches === 1, 'peer/standalone real native command'); } + } + }); + return { npm_lifecycle: true }; +} +function verifyCacheProcess(j, value, roots, commands) { + fields(value, ['schema', 'cell', 'rows', 'finalization'], 'cache/process'); + const expected = scenarioContract(j.cell).cache, rows = recordRows(value, 'authoring-public-cache-process/v1', j, expected, roots, commands); + const groups = new Map(), last = new Map(); + rows.forEach((r, i) => { + const s = expected[i], p = s.product; + agree(r.postinstall, null, 'shim has no npm postinstall'); + agree(r.after.prefix, r.before.prefix, 'shim preserves installed trees'); + for (const peer of cell(j.cell).products.filter(x => x !== p)) { + if (s.kind === 'peer-overlap') { agree(r.before.cache[peer], null, 'both peer namespaces start cold'); if (r.after.cache[peer]) binary(r.after.cache[peer], j, peer); } + else agree(r.after.cache[peer], r.before.cache[peer], 'cache namespace independence'); + } + if (s.kind.startsWith('invalid-')) { + agree(r.native_launches, 0, 'invalid locator never launches native'); agree(r.acquisitions, 0, 'invalid locator never acquires'); agree(r.commits, 0, 'invalid locator never commits'); + agree(r.after.cache, r.before.cache, 'invalid locator does not fallback even warm'); assert.ok(r.events.includes('locator-rejected'), 'locator rejection observed'); + if (s.kind === 'invalid-cold') agree(r.before.cache[p], null, 'invalid genuinely cold'); else assert.ok(r.before.cache[p], 'invalid genuinely warm'); + } else if (['cold', 'warm', 'repair', 'concurrent-cold', 'concurrent-warm', 'peer-overlap', 'waiter-owner'].includes(s.kind)) { + assert.ok(r.after.cache[p], 'checked cache result'); + if (['cold', 'peer-overlap'].includes(s.kind)) { agree(r.before.cache[p], null, 'fresh cold acquisition'); agree(r.acquisitions, 1, 'one cold acquisition'); agree(r.commits, 1, 'one checked commit'); } + if (['warm', 'concurrent-warm'].includes(s.kind)) { assert.ok(r.before.cache[p], 'warm cache present'); agree(r.after.cache, r.before.cache, 'warm exact cache identity'); agree([r.acquisitions, r.commits], [0, 0], 'warm zero acquisition/commit'); } + if (s.kind === 'repair') { assert.ok(r.before.cache[p], 'observed fixed corruption before repair'); assert.ok(r.events.includes('repair-before-launch') && r.events.indexOf('repair-before-launch') < r.events.indexOf('native'), 'owned corruption repaired before any execution'); agree([r.acquisitions, r.commits], [1, 1], 'checked repair acquisition/commit'); } + agree(r.native_launches, 1, 'one actual native command'); + } + if (s.group) { if (!groups.has(s.group)) groups.set(s.group, []); groups.get(s.group).push(r); } + if (!s.group && s.kind !== 'core') { + if (last.has(s.cache)) { + const before = s.kind === 'repair' ? { ...r.before.cache, [p]: r.after.cache[p] } : r.before.cache; + agree(before, last.get(s.cache), 'continuous serial cache history around fixed owned corruption'); + } + last.set(s.cache, r.after.cache); + } + }); + for (const [id, group] of groups) { + assert.ok(Math.max(...group.map(r => r.interval[0])) < Math.min(...group.map(r => r.interval[1])), `real overlapping requests:${id}`); + if (id.startsWith('cold-')) { + list(group, 4, 'four simultaneous cold requests'); + group.forEach(r => agree(Object.values(r.before.cache), cell(j.cell).products.map(() => null), 'all four start from the same cold state')); agree(group.reduce((n, r) => n + r.acquisitions, 0), 1, 'one concurrent acquisition'); + agree(group.reduce((n, r) => n + r.commits, 0), 1, 'one concurrent commit'); + group.forEach(r => agree(r.after.cache, group[0].after.cache, 'one valid resulting cache')); + } + if (id === 'peer-overlap') { list(group, 2, 'both overlapping peer requests'); agree(group.map(r => r.id.split('/')[0]), cell(j.cell).products, 'distinct overlapping products'); } + if (id.startsWith('waiter-')) assert.ok(group.some(r => r.events.includes('lock-owner')) && group.some(r => r.events.includes('cache-waiter')), 'genuine owner/waiter overlap'); + } + fields(value.finalization, ['rows', 'descendants', 'locks', 'late_errors', 'observation'], 'descendant finalization'); + agree(value.finalization.rows, [...scenarioContract(j.cell).npm, ...expected, ...scenarioContract(j.cell).installer].map(s => s.id), 'finalization complete process inventory'); + for (const k of ['descendants', 'locks', 'late_errors']) agree(value.finalization[k], [], `no remaining ${k}`); + sidecar(value.finalization.observation); + return { cache_process: true, children_reaped: true }; +} +const PLUGIN_SCHEMA = 'https://agent-plugins.org/schemas/1.0.0/plugin.schema.json'; +const MCP_SCHEMA = 'https://agent-plugins.org/schemas/1.0.0/mcp.schema.json'; +const PROFILES = freeze([ + ['agent-plugins/1.0.0', 'ff8ab5e392cc87bd88d87c060815a87490e51003', '97a658b7dca3ce1b4c2266b95da300fa51d9dc4ade59d73168e5f9104272da18'], + ['agent-skills/2026-09-06', '69ef37e9424c0a7ea9dd2293b559e43ec8176379', 'b9079c0c10b7930e8c6a20ff2bc10cda2a3343c55185120e3f1116a1a529b220'], + [PLUGIN_SCHEMA, '1.0.0', '0a4aad95ce337878ad38802ebf0daa3fde76abe3f65400c86bcbb1ec0b3ab883'], + [MCP_SCHEMA, '1.0.0', '6539175bfcdf43085855183e86da40ea94b166547a72b47ae9a0a390516d3acb'], + ['author-document-bounds/v1', '1', '4b8ab8fd50481ccd1a0b777dcbbfa06cf89516a5ea61ce09d56d6dd6a2c43004'] +].map(([id, revision, digest]) => ({ id, revision, digest: 'sha256:' + digest }))); +const SURFACE = freeze(['capabilities', 'compat', 'doctor', 'init', 'inspect', 'skills.init', 'skills.validate', 'test', 'validate', 'version'].map(x => 'author.' + x)); +function clientFacts() { + return [ + ['chatgpt', 'compatibility_projection', 'manual', 'projected', 'unsupported', 'projected', 'unsupported'], + ['claude', 'compatibility_projection', 'automatic', 'projected', 'projected', 'unsupported', 'unsupported'], + ['cline', 'native', 'automatic', 'native', 'native', 'unsupported', 'unsupported'], + ['codex', 'compatibility_projection', 'manual', 'projected', 'projected', 'unsupported', 'unsupported'], + ['copilot', 'native', 'manual', 'native', 'native', 'unsupported', 'native'], + ['cursor', 'native', 'manual', 'native', 'native', 'unsupported', 'native'], + ['gemini', 'native', 'manual', 'native', 'native', 'unsupported', 'unsupported'], + ['kiro', 'native', 'manual', 'native', 'native', 'unsupported', 'unsupported'], + ['opencode', 'prepared_package', 'automatic', 'prepared', 'prepared', 'unsupported', 'unsupported'], + ['vscode', 'prepared_package', 'manual', 'prepared', 'prepared', 'unsupported', 'prepared'], + ['windsurf', 'prepared_package', 'manual', 'prepared', 'prepared', 'unsupported', 'prepared'] + ].map(([client_id, package_mode, activation_mode, skill_support, mcp, app_support, extension_support]) => + ({ client_id, package_mode, activation_mode, scopes: ['user'], skill_support, mcp_transports: { stdio: mcp, 'streamable-http': mcp, sse: mcp }, app_support, extension_support })); +} +function optionalFields(v, required, optional, label) { + assert.ok(v && typeof v === 'object', label); + fields(v, [...required, ...optional.filter(k => Object.hasOwn(v, k))], label); +} +function outputJSON(text) { + textValue(text); assert.ok(text.length > 0, 'one JSON output'); + // Tokenize only to detect duplicate object keys/depth. JSON.parse owns syntax. + const stack = []; let match; + const tokens = /"(?:[^"\\\x00-\x1f]|\\(?:["\\/bfnrt]|u[0-9a-fA-F]{4}))*"|[{}\[\]]/g; + while ((match = tokens.exec(text))) { + const token = match[0]; + if (token === '{' || token === '[') { stack.push(token === '{' ? new Set() : null); assert.ok(stack.length <= 16, 'output depth'); } + else if (token === '}' || token === ']') stack.pop(); + else if (/^\s*:/.test(text.slice(tokens.lastIndex))) { + const keys = stack.at(-1), key = JSON.parse(token); assert.ok(keys && !keys.has(key), 'duplicate JSON result key'); keys.add(key); + } + } + return JSON.parse(text); +} +function componentFacts(lane, extra = true, malformed = false) { + const rows = [], skill = lane === 'skill' || lane.startsWith('hybrid-'); + const add = (type, name, requirements = [], status = 'pass') => rows.push({ id: 'sha256:' + c.digest(Buffer.from(`${type === 'skill' ? 'skill' : 'mcp'}:${name}`)), type, status, requirements }); + if (skill) add('skill', lane); + if (extra) add('skill', 'extra-skill', [], malformed ? 'fail' : 'pass'); + if (lane !== 'skill') add(lane.endsWith('stdio') ? 'mcp_stdio' : 'mcp_streamable-http', lane, + lane.endsWith('stdio') ? ['executable_unresolved', 'executable_path'] : ['remote_endpoint_uncontacted']); + return rows.sort((a, b) => a.id.localeCompare(b.id)); +} +const ASSESSMENTS = ['compatibility', 'toolchain', 'loadability', 'normative_conformance', 'host_safety', 'authoring_readiness', 'release_policy', 'runtime_evidence']; +function authorResult(v, want, j) { + const d = v.data, version = want.id === 'engine-version' || want.id === 'product-version'; + const optional = ['help', 'root', 'inspection', 'commands', 'withheld_path_ids', 'error', 'clients', 'capabilities', 'doctor_checks', 'product', 'product_version']; + optionalFields(d, [...ASSESSMENTS, 'schema', 'engine', 'revision', 'command', 'mode', 'identity', 'coverage', 'profiles', 'schema_ids', 'findings', + 'components', 'checks', 'committed', 'affected_paths', 'authoring_schema_version', 'engine_version', 'requested', 'effects', 'next_actions'], optional, 'author result fields'); + agree(d.schema, 'agentplugins-authoring-report/v1', 'report schema'); agree(d.engine, 'standard-first-slice/1', 'engine'); + agree(d.engine_version, d.engine, 'engine version'); agree(d.revision, j.identity.commit, 'engine F'); agree(d.authoring_schema_version, 1, 'author schema'); + const args = want.argv.slice(want.product === 'agentplugins' ? 1 : 0); + const op = want.id === 'retired-v1' ? 'author' : args[0] === '--help' ? 'author' : `author.${args[0]}${args[0] === 'skills' ? '.' + args[1] : ''}`; + agree(v.command, op, 'operation'); agree(d.command, op, 'report operation'); + const mutation = op === 'author.init' || op === 'author.skills.init'; + agree(d.mode, mutation ? 'local_mutation' : 'read', 'operation mode'); agree(d.requested, { operation: op, mode: d.mode }, 'requested operation'); + const committed = /\/(init|extra-skill)$/.test(want.id); + agree(d.committed, committed, 'commit boundary'); fields(d.effects, ['attempted', 'committed'], 'effects'); + agree(d.effects.committed, committed, 'public effects'); assert.equal(typeof d.effects.attempted, 'boolean'); + assert.ok(Array.isArray(d.findings) && d.findings.length <= 256, 'bounded findings'); + const ids = new Set(); + for (const f of d.findings) { + optionalFields(f, ['id', 'code', 'layer', 'rule', 'severity'], ['location', 'item_id'], 'finding'); + digestID(f.id); assert.ok(!ids.has(f.id), 'unique diagnostics'); ids.add(f.id); + Object.values(f).forEach(x => textValue(x, 4096)); assert.ok(['error', 'warning', 'info'].includes(f.severity), 'diagnostic severity'); + } + for (const name of ASSESSMENTS) { + fields(d[name], ['status', 'finding_ids'], 'separate policy assessment'); + assert.ok(['pass', 'fail', 'not_evaluated'].includes(d[name].status), 'policy state'); + assert.ok(Array.isArray(d[name].finding_ids) && d[name].finding_ids.every(id => ids.has(id)), 'assessment references real findings'); + agree([...new Set(d[name].finding_ids)].sort(), d[name].finding_ids, 'canonical finding references'); + } + agree(d.runtime_evidence, { status: 'not_evaluated', finding_ids: [] }, 'offline runtime not evaluated'); + agree(d.release_policy, { status: 'not_evaluated', finding_ids: [] }, 'release policy not inferred'); + fields(d.coverage, ['components_requested', 'skills_enumerated', 'inventory_complete', 'tree_complete', 'plugin', 'mcp', 'skills', 'filesystem', 'facts_complete'], 'coverage'); + for (const k of ['components_requested', 'skills_enumerated', 'inventory_complete', 'tree_complete', 'facts_complete']) assert.equal(typeof d.coverage[k], 'boolean'); + for (const k of ['plugin', 'mcp', 'skills', 'filesystem']) assert.ok(['pass', 'fail', 'not_evaluated'].includes(d.coverage[k]), 'coverage state'); + optionalFields(d.identity, ['scope_algorithm', 'read_profile', 'tree_exclusions'], ['scope_digest', 'tree_algorithm', 'tree_digest', 'manifest_digest'], 'project identity'); + const project = want.lane && !want.id.endsWith('/existing') && want.id !== 'installer-flag'; + if (project) { + agree(d.effects.attempted, true, 'entered actual project operation'); + agree(d.identity.scope_algorithm, 'agentplugins-captured-input-sha256-v1', 'scope algorithm'); + agree(d.identity.tree_algorithm, 'agentplugins-tree-sha256-v1', 'tree algorithm'); + agree(d.identity.read_profile, `packageview-local-${cell(j.cell).target.split('-')[0]}-v1`, 'recorded host read profile'); + agree(d.identity.tree_exclusions, ['root .git', 'root non-directory .plugin-kit-ai.lock'], 'exact tree exclusions'); + for (const k of ['scope_digest', 'tree_digest', 'manifest_digest']) digestID(d.identity[k]); + agree(d.profiles, PROFILES, 'embedded profiles'); + agree(d.schema_ids, (want.lane === 'skill' ? [PLUGIN_SCHEMA] : [MCP_SCHEMA, PLUGIN_SCHEMA]).sort(), 'exact schema inventory'); + for (const k of ['components_requested', 'skills_enumerated', 'inventory_complete', 'tree_complete', 'facts_complete']) agree(d.coverage[k], true, 'complete captured facts'); + const malformed = want.id === 'malformed-skill'; + for (const k of ['plugin', 'filesystem']) agree(d.coverage[k], 'pass', 'complete core/filesystem coverage'); + agree(d.coverage.mcp, want.lane === 'skill' ? 'not_evaluated' : 'pass', 'MCP coverage'); + agree(d.coverage.skills, 'pass', 'complete Skills capture separate from conformance'); + agree(d.loadability.status, 'pass', 'valid Skill sibling remains loadable'); + agree(d.normative_conformance.status, malformed ? 'fail' : 'pass', 'normative conformance'); + agree(d.host_safety.status, 'pass', 'host safety separate'); + agree(d.authoring_readiness.status, malformed ? 'fail' : 'pass', 'authoring readiness'); + agree(d.components, componentFacts(want.lane, !want.id.endsWith('/init'), malformed), 'exact Skill/MCP components and boundary'); + fields(d.inspection, ['name', 'version', 'schema', 'components'], 'inspection'); + agree([d.inspection.name, d.inspection.version, d.inspection.schema], [want.lane, '0.1.0', PLUGIN_SCHEMA], 'package identity'); + const names = d.inspection.components.map(x => { + optionalFields(x, ['id', 'type'], ['name', 'namespace', 'executable', 'executable_kind'], 'display component'); + digestID(x.id); Object.values(x).forEach(v => textValue(v, 256)); return [x.id, x.type, x.name]; + }); + agree(names, d.components.map(x => [x.id, x.type, x.id === 'sha256:' + c.digest(Buffer.from('skill:extra-skill')) ? 'extra-skill' : want.lane]), 'component display names'); + } else { + agree(d.profiles, [], 'no invented project profiles'); agree(d.schema_ids, [], 'no invented project schemas'); + agree(d.components, [], 'no project components'); + } + if (d.doctor_checks) { assert.ok(Array.isArray(d.doctor_checks) && d.doctor_checks.length <= 256); d.doctor_checks.forEach(x => { optionalFields(x, ['id', 'status', 'action'], ['item_id'], 'doctor check'); Object.values(x).forEach(v => textValue(v, 8192)); assert.ok(['pass', 'fail', 'not_evaluated'].includes(x.status)); }); } + if (d.withheld_path_ids) { assert.ok(Array.isArray(d.withheld_path_ids) && d.withheld_path_ids.length <= 256); d.withheld_path_ids.forEach(digestID); } + if (d.root !== undefined) { textValue(d.root, 4096); assert.fail('absolute/root disclosure was not requested'); } + if (want.id.endsWith('/doctor')) agree(d.toolchain.status, want.lane === 'skill' ? 'pass' : 'not_evaluated', 'doctor offline boundary'); + else agree(d.toolchain.status, 'not_evaluated', 'no implicit toolchain proof'); + if (want.id.endsWith('/compat')) { + agree(d.compatibility.status, 'pass', 'static compatibility'); list(d.clients, 2, 'two explicit clients'); + d.clients.forEach((client, i) => { + fields(client, ['client_id', 'capabilities', 'components', 'limitations'], 'compat client'); agree(client.client_id, ['claude', 'codex'][i], 'client order'); + agree(client.capabilities, clientFacts().find(x => x.client_id === client.client_id), 'client capability facts'); + const counts = { skill: 0, mcp_server: 0 }; + const components = d.components.map(x => ({ kind: x.type === 'skill' ? 'skill' : 'mcp_server', index: 0, support: 'projected' })) + .sort((a, b) => a.kind.localeCompare(b.kind)).map(x => ({ ...x, index: ++counts[x.kind] })); + agree(client.components.map(({ kind, index, support }) => ({ kind, index, support })), components, 'compat exact components'); + client.components.forEach(x => optionalFields(x, ['kind', 'index', 'support'], ['limitations'], 'compat component')); + agree(client.limitations, ['static_adapter_support_only', 'installation_not_checked', 'authentication_not_checked', 'runtime_not_checked', 'client_version_not_checked', 'catalog_publication_not_checked', ...(i === 1 ? ['manual_activation_required'] : [])], 'compat evidence limits'); + }); + } else agree(d.compatibility.status, 'not_evaluated', 'no implicit compatibility'); + if (want.id === 'capabilities') { + fields(d.capabilities, ['schemas', 'profiles', 'clients', 'commands', 'evidence_limits'], 'capability inventory'); + agree(d.capabilities.schemas, PROFILES.slice(2, 4).map(({ id, digest }) => ({ id, digest })), 'embedded schema pins'); + agree(d.capabilities.profiles, PROFILES, 'capability profiles'); agree(d.capabilities.clients, clientFacts(), 'complete client inventory'); + agree(d.capabilities.commands, SURFACE, 'complete capability commands'); + agree(d.capabilities.evidence_limits, ['static_only', 'no_path_lookup', 'no_executable_version_probe', 'no_runtime_or_oauth_evidence', 'native_files_metadata_only'], 'capability limits'); + } + if (['author-help', 'capabilities', 'engine-version', 'product-version'].includes(want.id)) agree(d.commands, SURFACE, 'implemented command surface'); + if (version) agree([d.product, d.product_version], [want.product, j.identity.versions[want.product]], 'kit product version'); + if (d.help) { fields(d.help, ['use', 'flags', 'guidance'], 'help'); textValue(d.help.use, 4096); textValue(d.help.guidance, 8192); assert.ok(Array.isArray(d.help.flags) && d.help.flags.every(x => typeof x === 'string'), 'help flags'); } + if (d.error) { fields(d.error, ['code', 'action'], 'operation error'); textValue(d.error.code, 256); textValue(d.error.action, 8192); } + for (const k of ['affected_paths', 'next_actions', 'checks']) assert.ok(Array.isArray(d[k]) && d[k].length <= 256, 'bounded result lists'); + d.affected_paths.forEach(x => { textValue(x, 4096); assert.ok(!x.startsWith('/') && !x.split('/').includes('..'), 'relative affected path'); }); + if (!committed) agree(d.affected_paths, [], 'read/failed operation no affected files'); else assert.ok(d.affected_paths.length > 0, 'committed actual paths'); + d.next_actions.forEach(x => { optionalFields(x, ['code', 'message'], ['operation'], 'next action'); Object.values(x).forEach(v => textValue(v, 8192)); }); + d.checks.forEach(x => { fields(x, ['id', 'status', 'finding_ids'], 'static check'); textValue(x.id, 256); assert.ok(['pass', 'fail', 'not_evaluated'].includes(x.status)); assert.ok(x.finding_ids.every(id => ids.has(id))); }); + if (want.id.endsWith('/test')) agree(d.checks.map(x => [x.id, x.status]), [['portable_configuration', 'pass'], ['package_hygiene', 'pass'], ['static_skills', 'pass'], ['static_mcp', want.lane === 'skill' ? 'not_evaluated' : 'pass'], ['runtime', 'not_evaluated']], 'complete static checks'); +} +function normalizeResult(v) { + const copy = structuredClone(v); + // Only product/version fields and displayed invocation prefix are allowed to differ. + delete copy.data.product; delete copy.data.product_version; + if (copy.data.help) copy.data.help.use = copy.data.help.use.replace(/^(plugin-kit-ai|agentplugins author)(?= |$)/, 'AUTHOR'); + return copy; +} +// Scenario records stay below 1MiB; ordered process rows use bounded transcript +// sidecars. Long supported workspace paths must not inflate the record envelope. +function evidenceFiles(name, value) { + const files = {}; + if (['npm-lifecycle.json', 'cache-process.json', 'installer.json'].includes(name) && Array.isArray(value.rows)) { + const refs = []; let shard = [], size = 3; + const flush = () => { + if (!shard.length) return; + const file = `sidecars/${name.slice(0, -5)}-rows-${refs.length}.json`, bytes = c.encode(shard); + assert.ok(bytes.length <= TRANSCRIPT_LIMIT, '16MiB transcript shard'); files[file] = bytes; + refs.push({ path: file, size: bytes.length, sha256: c.digest(bytes) }); shard = []; size = 3; + }; + for (const row of value.rows) { + const bytes = c.encode(row); assert.ok(bytes.length <= LIMIT, '1MiB process record'); + if (size + bytes.length + 1 > TRANSCRIPT_LIMIT) flush(); + shard.push(row); size += bytes.length + 1; + } + flush(); value = { ...value, rows: { shards: refs } }; + } + const bytes = c.encode(value); + assert.ok(bytes.length <= (name === 'commands.json' ? TRANSCRIPT_LIMIT : LIMIT), 'bounded evidence file'); + files[name] = bytes; return files; +} +function expandEvidence(name, value, root, budget = { size: 0 }) { + if (!['npm-lifecycle.json', 'cache-process.json', 'installer.json'].includes(name) || value.rows === undefined || Array.isArray(value.rows)) return value; + fields(value.rows, ['shards'], 'ordered row transcript index'); + assert.ok(Array.isArray(value.rows.shards) && value.rows.shards.length <= 128, 'bounded shard inventory'); + const rows = []; + value.rows.shards.forEach((ref, i) => { + sidecar(ref); agree(ref.path, `sidecars/${name.slice(0, -5)}-rows-${i}.json`, 'fixed row shard name'); + budget.size += ref.size; assert.ok(budget.size <= AGGREGATE_LIMIT, '128MiB transcript aggregate'); + const bytes = pin(path.join(root, ref.path), ref.sha256, TRANSCRIPT_LIMIT); agree(bytes.length, ref.size, 'row shard size'); + const shard = bounded(bytes, TRANSCRIPT_LIMIT); assert.ok(Array.isArray(shard) && shard.length > 0, 'nonempty row shard'); + for (const row of shard) { assert.ok(c.encode(row).length <= LIMIT, '1MiB process record'); rows.push(row); } + }); + const expanded = { ...value, rows }; + agree(c.encode(value), evidenceFiles(name, expanded)[name], 'canonical row shard partition'); return expanded; +} +// Fixed publicInit lanes only: exact scaffold bytes, not a configurable template engine. +function generatedFiles(lane) { + assert.ok(LANES.includes(lane), 'fixed generated lane'); + const hybrid = lane.startsWith('hybrid-'), stdio = lane.endsWith('stdio'), remote = lane.endsWith('remote'); + const description = hybrid ? 'An Agent Plugins package with a Skill and an MCP server.' : lane === 'skill' ? 'A Skill for documentation and task guidance.' : remote ? 'An Agent Plugins package with a remote MCP server.' : 'An Agent Plugins package with a local Node MCP server.'; + const json = value => JSON.stringify(value, null, 2) + '\n'; + const files = { + 'plugin.json': json({ $schema: PROFILES[2].id, description, name: lane, version: '0.1.0' }), + '.gitignore': 'node_modules/\n.DS_Store\n', + 'README.md': `# ${lane}\n\n${description}\n\nThis package uses Agent Plugins 1.0: \`plugin.json\`, with portable components in \`skills/\` and/or \`mcp.json\`.\n` + + (stdio ? '\nThe stdio server requires Node >=22 and the official MCP SDK pinned in package-lock.json. Dependency installation and runtime execution are separate, explicit author actions. Creation performs neither; runtime behavior has not been tested.\n' : remote ? '\nThe remote MCP URL is configuration only. Creation does not contact the endpoint or verify authentication or runtime behavior.\n' : ''), + 'skills/extra-skill/SKILL.md': '---\nname: "extra-skill"\ndescription: "Use for extra documentation requests"\n---\n\n# extra-skill\n\nUse for extra documentation requests\n' + }; + if (hybrid || lane === 'skill') files[`skills/${lane}/SKILL.md`] = `---\nname: ${lane}\ndescription: ${JSON.stringify(description)}\n---\n\n# ${lane}\n\n${description}\n\nUse this skill when the request matches its description. Clarify missing requirements before taking action and report the result.\n`; + if (remote || stdio) files['mcp.json'] = json({ $schema: PROFILES[3].id, mcpServers: { [lane]: stdio ? { args: ['${PLUGIN_ROOT}/src/server.mjs'], command: 'node', type: 'stdio' } : { type: 'streamable-http', url: 'https://docs.example.com/mcp' } } }); + if (stdio) { + // Existing source-frozen embedded npm fixtures; never install or resolve them. + for (const name of ['package.json', 'package-lock.json']) { + const bytes = c.readFile(path.resolve(__dirname, '../../../cli/plugin-kit-ai/internal/authoring/scaffold/templates', name), LIMIT).toString('utf8'); + const needle = name === 'package.json' ? '"name":"agent-plugin-template"' : '"name": "agent-plugin-template"'; + agree(bytes.split(needle).length - 1, name === 'package.json' ? 1 : 2, 'fixed embedded root names'); + files[name] = bytes.split(needle).join(needle.replace('agent-plugin-template', lane)); + } + files['src/server.mjs'] = `import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\nimport { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';\n\nconst server = new McpServer({ name: "${lane}", version: '0.1.0' });\nserver.registerTool('hello', { description: 'Return a greeting', inputSchema: {} }, async () => ({\n content: [{ type: 'text', text: "Hello from ${lane}!" }],\n}));\nawait server.connect(new StdioServerTransport());\n`; + } + return Object.fromEntries(Object.entries(files).map(([name, body]) => [name, Buffer.from(body)])); +} +function generatedTreeIdentity(entries, lane, key) { + const files = generatedFiles(lane), directories = new Set(['.']); + for (const name of Object.keys(files)) for (let dir = path.posix.dirname(name); dir !== '.'; dir = path.posix.dirname(dir)) directories.add(dir); + const actual = entries.filter(e => e.path === lane || e.path.startsWith(lane + '/')).map(e => ({ ...e, path: e.path === lane ? '.' : e.path.slice(lane.length + 1) })); + agree(actual.map(e => e.path).sort(), [...directories, ...Object.keys(files)].sort(), 'fixed generated file/directory closure'); + const windows = cell(key).target.startsWith('windows-'); + for (const e of actual) { + const directory = directories.has(e.path); + agree(e.kind, directory ? 'directory' : 'file', 'generated entry type'); + // Node's Windows stat reports writable files 0666 and directories 0777; + // packageview uses that host read profile, including file execute bits. + agree(e.mode, windows ? directory ? 0o777 : 0o666 : directory ? 0o755 : 0o644, 'generated host mode'); + if (!directory) { agree(e.size, files[e.path].length, 'generated file size'); agree(e.sha256, c.digest(files[e.path]), 'generated file content'); } + } + // Ordinary agentplugins-tree-sha256-v1 framing (DigestCaptured). Root is + // excluded; directories, including empty ones, are entries. Bytes below are + // bound to captured size/hash above; claimed report digests are never inputs. + const chunks = [], length = n => { const b = Buffer.alloc(8); b.writeBigUInt64BE(BigInt(n)); return b; }; + const frame = value => { const b = Buffer.from(value); chunks.push(length(b.length), b); }; + frame('agentplugins.package-tree\0sha256\0v1'); + for (const e of actual.filter(e => e.path !== '.').sort((a, b) => a.path < b.path ? -1 : a.path > b.path ? 1 : 0)) { + const body = e.kind === 'file' ? files[e.path] : Buffer.alloc(0); + for (const field of ['entry', e.path, e.kind, e.kind === 'directory' ? '040000' : e.mode & 0o111 ? '100755' : '100644', '']) frame(field); + chunks.push(length(body.length), body); + } + return 'sha256:' + c.digest(Buffer.concat(chunks)); +} +function verifyResults(j, evidence) { + for (const name of EVIDENCE) if (Object.hasOwn(evidence, name)) evidenceFiles(name, evidence[name]); + const expected = commandContract(j.cell), rows = evidence['commands.json']; list(rows, expected.length, 'exact ordered C3 core command rows'); + const payloads = new Map(), identityByProject = new Map(); rows.forEach((row, i) => { - const want = expected[i]; fields(row, ["product", "id", "argv", "cwd", "status", "signal", "stdout", "stderr"], "C3 command result"); - for (const key of ["product", "id", "argv", "status"]) agree(row[key], want[key], `command ${key}`); - const parent = j.projects[want.product]; - const cwd = want.scenario === "projects" ? parent : path.join(path.dirname(parent), `${want.product} malformed-skill ü`); - agree(row.cwd, cwd, "fixed cwd"); agree(row.signal, null, "complete command (no signal)"); agree(row.stderr, "", "clean stderr"); - assert.ok(typeof row.stdout === "string" && Buffer.byteLength(row.stdout) <= LIMIT, "bounded stdout"); - if (want.id === "product-help") { assert.ok(row.stdout.length > 50, "product help"); return; } - const v = JSON.parse(row.stdout); agree(v.schema_version, 1, "result schema"); - agree(v.result, want.status === 0 ? "success" : "failure", "result status"); - assert.ok(v.data && typeof v.data === "object" && !Array.isArray(v.data), "result data"); - if (want.author) { - const args = want.argv.slice(want.product === "agentplugins" ? 1 : 0); - agree(v.command, args[0] === "--help" ? "author" : `author.${args[0]}${args[0] === "skills" ? `.${args[1]}` : ""}`, "author operation"); - agree(v.data.revision, j.identity.commit, "engine F"); agree(v.data.engine, "standard-first-slice/1", "engine"); - agree(v.data.authoring_schema_version, 1, "author schema"); agree(v.data.runtime_evidence?.status, "not_evaluated", "offline runtime boundary"); - if (want.lane) agree(v.data.committed, /\/(init|extra-skill)$/.test(want.id), "mutation boundary"); - if (want.id.endsWith("/doctor")) agree(v.data.toolchain?.status, want.lane === "skill" ? "pass" : "not_evaluated", "doctor boundary"); - } else if (want.id === "product-version") agree(v.data[want.product === "agentplugins" ? "version" : "product_version"], j.identity.versions[want.product], "product version"); + const want = expected[i]; fields(row, ['product', 'id', 'argv', 'cwd', 'status', 'signal', 'stdout', 'stderr'], 'C3 command result'); + for (const k of ['product', 'id', 'argv', 'status']) agree(row[k], want[k], `command ${k}`); + agree(row.cwd, commandCwd(j, want), 'fixed cwd'); agree(row.signal, null, 'complete command'); agree(row.stderr, '', 'clean stderr'); textValue(row.stdout); + if (want.id === 'product-help') { assert.ok(row.stdout.includes(want.product) && row.stdout.length > 50, 'real product help'); return; } + const v = outputJSON(row.stdout); fields(v, ['schema_version', 'command', 'result', 'data'], 'one output envelope'); + agree(v.schema_version, 1, 'JSON schema'); agree(v.result, want.status === 0 ? 'success' : 'failure', 'result status'); + if (want.id.startsWith('installer/')) return; // Checked by the mandatory fixed public installer facade below. + if (want.author || want.product === 'plugin-kit-ai') authorResult(v, want, j); + else { agree(v.command, 'version', 'agent product version operation'); assert.ok(v.data && typeof v.data === 'object'); agree(v.data.version, j.identity.versions.agentplugins, 'agent product version'); } + if (want.lane && !/\/(init|existing)$/.test(want.id) && !['malformed-skill', 'installer-flag'].includes(want.id)) { + const key = `${want.product}/${want.lane}`; + if (identityByProject.has(key)) agree(v.data.identity, identityByProject.get(key), 'unchanged read-only project identity'); + else identityByProject.set(key, v.data.identity); + } + payloads.set(`${want.product}/${want.id}`, v); + }); + fields(evidence['projects.json'], cell(j.cell).products, 'final canonical projects'); + for (const p of cell(j.cell).products) { + const snapshot = tree(evidence['projects.json'][p], j.cell, j.projects[p]); + assert.ok(snapshot.entries.every(e => e.path === '.' || LANES.some(lane => e.path === lane || e.path.startsWith(lane + '/'))), 'only generated lane entries'); + agree(snapshot.entries.filter(e => e.kind === 'directory' && e.path !== '.' && !e.path.includes('/')).map(e => e.path).sort(), [...LANES].sort(), 'exact five canonical projects'); + for (const lane of LANES) { + const capturedDigest = generatedTreeIdentity(snapshot.entries, lane, j.cell); + const identity = identityByProject.get(`${p}/${lane}`); assert.ok(identity, 'project read identity present'); + const manifest = snapshot.entries.find(e => e.path === `${lane}/plugin.json`); + agree(identity.tree_digest, capturedDigest, 'captured generated tree identity'); + agree(identity.manifest_digest, 'sha256:' + manifest.sha256, 'observed manifest identity'); + } + } + if (cell(j.cell).products.length === 2) { + for (const want of expected.filter(r => r.product === 'agentplugins' && r.author)) + agree(normalizeResult(payloads.get(`agentplugins/${want.id}`)), normalizeResult(payloads.get(`plugin-kit-ai/${want.id}`)), 'pair JSON parity including diagnostics/readiness/digests'); + agree(evidence['projects.json'].agentplugins.entries, evidence['projects.json']['plugin-kit-ai'].entries, 'pair generated trees bytes/modes/empty directories'); + } + return { fixed_commands: true, pair_parity: true, projects_preserved: true }; +} +function verifyInstaller(j, value, roots, commands, observation, facade) { + fields(value, ['schema', 'cell', 'rows', 'assessment', 'readbacks'], 'installer evidence'); + recordRows(value, 'authoring-public-installer/v1', j, scenarioContract(j.cell).installer, roots, commands); + if (cell(j.cell).node === 18) { agree(value.assessment, null, 'kit has no installer assessment'); agree(value.readbacks, [], 'kit has no installer readbacks'); return { production_installer: true }; } + // Assessment/readback internals belong exclusively to the reviewed owner facade. + // They are bounded opaque sidecars here, not caller booleans or copied security logic. + sidecar(value.assessment); list(value.readbacks, 18, 'all eighteen installer readbacks'); value.readbacks.forEach(sidecar); + const checked = facade.verifyPublicInstaller({ cell: j.cell, identity: j.identity, subjects: j.subjects, + commands: value.rows, assessment: value.assessment, readbacks: value.readbacks, observation }); + agree(checked, { assessment: value.assessment, readbacks: value.readbacks }, 'checked installer evidence, never boolean success'); + return { production_installer: true }; +} +function journeyRoots(j) { + const p = hostPath(j.cell), output = p.dirname(p.dirname(j.projects[cell(j.cell).products[0]])); + return rootsFor(output, j.cell); +} +function verifyJourney(local) { + const { record: j, evidence } = local, roots = journeyRoots(j); + const result = verifyResults(j, evidence); + Object.assign(result, verifyNpmLifecycle(j, evidence['npm-lifecycle.json'], roots, evidence['commands.json']), + verifyCacheProcess(j, evidence['cache-process.json'], roots, evidence['commands.json'])); + const api = requireFacades(j.cell), all = [...evidence['npm-lifecycle.json'].rows, ...evidence['cache-process.json'].rows, ...evidence['installer.json'].rows]; + // All core effects share the same original project roots. Bind observed history + // to the retained final snapshots, including every read and installer row. + const coreRows = all.filter(r => r.command !== null).sort((a, b) => { + const order = [...scenarioContract(j.cell).cache, ...scenarioContract(j.cell).installer].map(s => s.command); + return order.indexOf(a.command) - order.indexOf(b.command); + }); + let projectState; + for (const r of coreRows) { + if (projectState !== undefined) agree(r.before.projects, projectState, 'continuous original project history'); + const w = commandContract(j.cell)[r.command]; + if (/\/(init|extra-skill)$/.test(w.id)) assert.notEqual(r.before.projects, r.after.projects, 'actual authoring mutation changed tree'); + projectState = r.after.projects; + } + agree(projectState, c.digest(c.encode(evidence['projects.json'])), 'final original project snapshots'); + for (const scenario of [...scenarioContract(j.cell).npm, ...scenarioContract(j.cell).cache]) { + if (['install', 'uninstall', 'reinstall', 'literal-argv', 'cancel', 'waiter-cancel', 'invalid-cold', 'invalid-warm', 'core'].includes(scenario.kind)) continue; + const row = all.find(r => r.id === scenario.id), version = evidence['commands.json'].find(r => r.product === scenario.product && r.id === 'product-version'); + agree(row.stdout, { size: Buffer.byteLength(version.stdout), sha256: c.digest(Buffer.from(version.stdout)) }, 'supplementary native version result'); + agree(row.stderr, { size: 0, sha256: c.digest(Buffer.alloc(0)) }, 'supplementary clean stderr'); + } + const finalization = evidence['cache-process.json'].finalization; + const observed = api['public-process-observation'].verifyPublicObservation({ cell: j.cell, tools: j.tools, subjects: j.subjects, rows: all, finalization }); + agree(observed, { rows: all, finalization }, 'checked observation evidence, never boolean success'); + Object.assign(result, verifyInstaller(j, evidence['installer.json'], roots, evidence['commands.json'], finalization, api['public-installer-evidence'])); + agree(Object.fromEntries(ASSERTIONS.map(k => [k, result[k]])), j.assertions, 'all seven assertions recomputed'); + return local; +} +function sourceSeal(repo, source, provision) { + const git = provision.controllers[cell(provision.key).target].git.path; + const run = args => require('node:child_process').execFileSync(git, args, { cwd: repo, env: { PATH: path.dirname(git), LANG: 'C', GIT_CONFIG_NOSYSTEM: '1', GIT_CONFIG_GLOBAL: process.platform === 'win32' ? 'NUL' : '/dev/null' }, maxBuffer: TRANSCRIPT_LIMIT }).toString(); + agree(run(['rev-parse', 'HEAD']).trim(), source, 'exact executing source F'); agree(run(['status', '--porcelain=v1', '--untracked-files=all']), '', 'clean exact source'); + return run(['ls-files', '-z']).split('\0').filter(Boolean).map(name => { + const file = path.join(repo, name), st = fs.lstatSync(file); + assert.ok(st.isFile() || st.isSymbolicLink(), 'source file/link'); + return { name, mode: st.mode, sha256: c.digest(st.isSymbolicLink() ? Buffer.from(fs.readlinkSync(file)) : fs.readFileSync(file)) }; }); - // A complete structural transcript still cannot attest process observation, - // installer assessment, npm postinstall, parity or final child quiescence. - throw new Error(MISSING); } -function readJourney(value) { const local = readJourneyInputs(value); verifyJourney(local); } +function admitProducerInputs(r, api) { + const inputBytes = c.readFile(r.input_file, LIMIT), input = contract.decodeInputs(inputBytes); + agree(r.selected, { tag: input.products.agentplugins.tag, ref: `refs/tags/${input.products.agentplugins.tag}`, source: input.identity.commit, versions: input.identity.versions }, 'producer selected I'); + agree(r.workflow_sha, input.identity.commit, 'producer F'); producer(r.producer, input); + const admitted = api.readPublicInputs({ input: inputBytes, selected: r.selected, workflow_sha: r.workflow_sha, stage: r.stage, + repo: r.repo, work_parent: r.work_parent, tools: r.tools }); + fields(admitted, ['stage', 'input'], 'authenticated cross-host intake'); + agree(admitted.input.input, input, 'authenticated I'); + const stageBytes = pin(path.join(admitted.stage.root, 'completion.json'), r.stage.sha256), stages = require('./stage-authoring-npm'); + const stage = stages.decodeStage(stageBytes, inputBytes); agree(admitted.stage.record, stage, 'authenticated S'); + agree(stage.native_inputs.sha256, c.digest(inputBytes), 'S binds original I'); + agree([stage.producer.run_id, stage.producer.run_attempt], [r.stage.artifact.run_id, r.stage.artifact.run_attempt], 'exact stage attempt'); + assert.ok(![stage.producer.run_id, input.producer.run_id, input.preparation.artifact.run_id].includes(r.producer.run_id), 'separate public producer'); + const retained = []; + for (const [admission, count, kind] of [[admitted.stage, 3, 'stage'], [admitted.input, 19, 'input']]) { + c.safeDirectory(admission.root); list(admission.subjects, count, 'original subject multiset'); + const seen = new Set(); + for (const row of admission.subjects) { + const relative = path.relative(admission.root, row.file); + assert.ok(relative && !relative.startsWith('..') && !path.isAbsolute(relative) && !seen.has(relative), 'distinct contained original subject'); seen.add(relative); + const bytes = pin(row.file, row.sha256, contract.MAX_NATIVE_BYTES); + retained.push({ kind, relative, file: row.file, sha256: row.sha256, mode: fs.statSync(row.file).mode & 0o777, bytes }); + } + } + assert.ok(retained.some(x => x.kind === 'input' && x.relative === contract.INPUT_FILE && x.bytes.equals(inputBytes)), 'original I subject'); + for (const product of c.PRODUCTS) { + agree(Object.keys(stage.generated[product]).length, 17, 'both authenticated exact seventeen-entry packs'); + const pack = retained.find(x => x.kind === 'stage' && x.relative === stage.packs[product].file); + assert.ok(pack, 'both original pack subjects'); agree(pack.sha256, stage.packs[product].sha256, 'pack SHA256'); agree(pack.bytes.length, stage.packs[product].size, 'pack size'); + const crypto = require('node:crypto'); + agree('sha512-' + crypto.createHash('sha512').update(pack.bytes).digest('base64'), stage.packs[product].integrity, 'pack SRI'); + agree(crypto.createHash('sha1').update(pack.bytes).digest('hex'), stage.packs[product].shasum, 'pack SHA1'); + for (const target of c.TARGETS) assert.ok(retained.some(x => x.kind === 'input' && x.relative === input.products[product].assets[target].file && + x.sha256 === input.products[product].assets[target].sha256), 'all twelve original native outer subjects'); + } + return { inputBytes, input, stageBytes, stage, retained, admitted }; +} +function actualState(j, roots, scenario) { + const bridge = require('./packed-installer-bridge'), scope = scopePaths(j, scenario, roots), p = hostPath(j.cell); + const snap = root => bridge.snapshot(root, true).sha256, prefix = {}, cache = {}; + for (const product of cell(j.cell).products) { + const packageRoot = p.join(scope.prefix, ...(j.cell.startsWith('windows-') ? [] : ['lib']), 'node_modules', contract.PACKAGES[product]); + const kinds = j.cell.startsWith('windows-') ? [['posix', ''], ['cmd', '.cmd'], ['powershell', '.ps1']] : [['posix', '']]; + const shims = kinds.map(([kind, suffix]) => p.join(scope.prefix, ...(j.cell.startsWith('windows-') ? [] : ['bin']), product + suffix)); + if (!fs.existsSync(packageRoot)) { assert.ok(shims.every(file => { try { fs.lstatSync(file); return false; } catch (e) { if (e.code === 'ENOENT') return true; throw e; } }), 'removed all shims, including dangling links'); prefix[product] = null; } + else prefix[product] = { tree: snap(packageRoot), shims: kinds.map(([kind], i) => { + const file = shims[i], st = fs.lstatSync(file), target = st.isSymbolicLink() ? fs.readlinkSync(file) : null; + const resolved = fs.realpathSync(file); assert.ok(resolved.startsWith(packageRoot + p.sep) || resolved === file, 'contained npm shim'); + return { kind, path: file, sha256: c.digest(fs.readFileSync(file)), mode: st.mode & 0o777, target }; + }) }; + const release = { descriptor: { schema: contract.DESCRIPTOR_SCHEMA, identity: j.identity, candidate_sha256: j.candidate_sha256 }, + version: j.identity.versions[product], asset: j.subjects[product][cell(j.cell).target] }; + const file = require('../lib/public-authoring').cachePath(p.join(scope.home, '.cache', 'universal-agent-plugins'), product, cell(j.cell).target, release); + cache[product] = fs.existsSync(file) ? { path: file, sha256: c.digest(c.readFile(file, contract.MAX_NATIVE_BYTES)), size: fs.statSync(file).size, mode: fs.statSync(file).mode & 0o777 } : null; + } + return { projects: c.digest(c.encode(Object.fromEntries(cell(j.cell).products.map(p => [p, bridge.snapshot(j.projects[p])])))), prefix, cache, client: snap(roots.client), state: snap(roots.state), + inputs: c.digest(c.encode([snap(roots.input), snap(roots.stage)])) }; +} +function verifySidecars(evidence, root) { + const rows = [...evidence['npm-lifecycle.json'].rows, ...evidence['cache-process.json'].rows, ...evidence['installer.json'].rows]; + const refs = rows.flatMap(r => [r.observation, ...(r.postinstall ? [r.postinstall.observation] : [])]); + refs.push(evidence['cache-process.json'].finalization.observation); + if (evidence['installer.json'].assessment) refs.push(evidence['installer.json'].assessment, ...evidence['installer.json'].readbacks); + const names = new Map(); let total = 0; + for (const name of EVIDENCE) for (const [file, bytes] of Object.entries(evidenceFiles(name, evidence[name]))) { + if (file !== name) refs.push({ path: file, size: bytes.length, sha256: c.digest(bytes) }); + else total += bytes.length; + } + for (const ref of refs) { + sidecar(ref); + if (names.has(ref.path)) { agree(names.get(ref.path), ref, 'same sidecar pin'); continue; } + names.set(ref.path, ref); total += ref.size; assert.ok(total <= AGGREGATE_LIMIT, '128MiB complete evidence closure'); + const body = pin(path.join(root, ref.path), ref.sha256, TRANSCRIPT_LIMIT); agree(body.length, ref.size, 'sidecar size'); + } + assert.ok(total <= AGGREGATE_LIMIT, '128MiB complete evidence closure'); + agree(fs.readdirSync(path.join(root, 'sidecars')).sort(), [...names.keys()].map(n => n.slice('sidecars/'.length)).sort(), 'exhaustive sidecar closure'); +} +function failureText(error) { + if (!error) return null; + const pending = [error], seen = new Set(); let text = '', count = 0; + while (pending.length && text.length < 120000 && count++ < 64) { + const current = pending.shift(); if (seen.has(current)) continue; seen.add(current); + text += String(current && current.stack || current).slice(0, 8192) + '\n'; + if (current && Array.isArray(current.errors)) pending.push(...current.errors.slice(0, 16)); + if (current && current.cause) pending.push(current.cause); + } + return text.slice(0, 120000) + (pending.length ? '\n[additional failure details bounded; journey failed]\n' : ''); +} +async function produceJourney(value) { + // Provision authority is checked before any untrusted record or output effect. + const provisioning = require('./public-authoring-tools'); + const controller = provisioning.requireController(process.platform === 'win32' ? `windows-${process.arch === 'x64' ? 'amd64' : 'arm64'}` : `${process.platform}-${process.arch === 'x64' ? 'amd64' : 'arm64'}`); + agree(controller, process.execPath, 'independently provisioned executing controller'); + fields(value, PRODUCE_FIELDS, 'closed producer request'); const r = value; + fixed(r.schema, 'authoring-public-produce/v1', 'producer schema'); cell(r.cell); tools(r.tools, cell(r.cell)); locator(r.stage); + fields(r.selected, ['tag', 'ref', 'source', 'versions'], 'selected source'); + for (const k of ['input_file', 'repo', 'work_parent', 'output']) absolute(r[k]); + agree(r.repo, path.resolve(__dirname, '../../..'), 'executing checkout'); + agree(r.tools.host, { platform: process.platform, arch: process.arch }, 'actual native host'); + const provision = provisioning.requireCellTools(r.cell), manifest = provisioning.readProvisioning(); + agree(r.tools.orchestrator_node, { path: controller, version: process.version, sha256: c.digest(c.readFile(controller)) }, 'controller comparison'); + for (const k of ['npm_node', 'shim_node', 'npm', 'go']) agree(r.tools[k], provision[k] === null ? null : Object.fromEntries(['path', 'sha256', 'version'].map(n => [n, provision[k][n]])), 'source-frozen cell tools'); + const api = requireFacades(r.cell), source = sourceSeal(r.repo, r.workflow_sha, { ...manifest, key: r.cell }); + c.safeDirectory(r.work_parent); c.safeDirectory(path.dirname(r.output)); assert.ok(!fs.existsSync(r.output), 'new owned output'); + disjoint([r.output, r.work_parent, r.repo, path.dirname(r.input_file)]); + const intake = admitProducerInputs(r, api['public-authoring-custody']), roots = rootsFor(r.output, r.cell); + disjoint([r.output, intake.admitted.input.root, intake.admitted.stage.root]); + for (const t of Object.values(r.tools).filter(t => t && t.path)) disjoint([r.output, t.path]); + disjoint([r.output, provision.npm.closure.root]); if (provision.mod_cache) disjoint([r.output, provision.mod_cache.root]); + const j = { schema: SCHEMA, status: 'completed', ...Object.fromEntries(['identity', 'authoring_mode', 'asset_scope', 'candidate_sha256', 'pair_marker_sha256', 'native_inputs', 'packs'].map(k => [k, intake.stage[k]])), + stage: r.stage, producer: r.producer, cell: r.cell, tools: r.tools, command_contract_sha256: c.digest(c.encode(commandContract(r.cell))), + subjects: Object.fromEntries(c.PRODUCTS.map(p => [p, intake.input.products[p].assets])), + projects: Object.fromEntries(cell(r.cell).products.map(p => [p, path.join(roots.projects, `${p} projects ü`)])), evidence: [], assertions: Object.fromEntries(ASSERTIONS.map(k => [k, true])) }; + // Installer availability is required before making output, npm or native effects. + if (api['public-installer-evidence']) api['public-installer-evidence'].requirePublicInstaller({ cell: r.cell, tools: r.tools, roots }); + fs.mkdirSync(r.output, { mode: 0o700 }); + try { + for (const root of Object.values(roots)) fs.mkdirSync(root, { mode: 0o700 }); + for (const retained of intake.retained) { + const file = path.join(roots[retained.kind], retained.relative); fs.mkdirSync(path.dirname(file), { recursive: true, mode: 0o700 }); + fs.writeFileSync(file, retained.bytes, { flag: 'wx', mode: retained.mode }); fs.chmodSync(file, retained.mode); + } + Object.values(j.projects).forEach(dir => fs.mkdirSync(dir, { mode: 0o700 })); + const scenarios = scenarioContract(r.cell), all = [...scenarios.npm, ...scenarios.cache, ...scenarios.installer]; + for (const scenario of all) { + const s = scopePaths(j, scenario, roots); + for (const dir of [s.prefix, s.home, path.join(s.home, 'tmp'), s.npmCache, s.cwd]) fs.mkdirSync(dir, { recursive: true, mode: 0o700 }); + for (const file of [s.userconfig, s.globalconfig]) if (!fs.existsSync(file)) fs.writeFileSync(file, '', { flag: 'wx', mode: 0o600 }); + } + const observed = new Map(), commands = new Array(commandContract(r.cell).length); let primary, terminal, finalError; + const recheck = () => { + provisioning.requireCellTools(r.cell); agree(sourceSeal(r.repo, r.workflow_sha, { ...manifest, key: r.cell }), source, 'source preserved'); + intake.retained.forEach(x => { for (const file of [x.file, path.join(roots[x.kind], x.relative)]) { + pin(file, x.sha256, contract.MAX_NATIVE_BYTES); agree(fs.statSync(file).mode & 0o777, x.mode, 'original and copied custody modes'); + } }); + }; + async function run(s, before) { + if (s.kind === 'repair') { + const b = before.cache[s.product]; assert.ok(b, 'owned exact repair target'); binary(b, j, s.product); + assert.ok(b.path.startsWith(scopePaths(j, s, roots).home + path.sep), 'owned corruption target only'); + fs.writeFileSync(b.path, CORRUPTION_BYTES, { flag: 'w' }); + before = actualState(j, roots, s); binary(before.cache[s.product], j, s.product, true); + } + if (s.kind === 'core' && commandContract(j.cell)[s.command].id === 'malformed-skill') { + const destination = path.join(scopePaths(j, s, roots).cwd, 'skill'); + fs.cpSync(path.join(j.projects[s.product], 'skill'), destination, { recursive: true, errorOnExist: true, force: false }); + fs.writeFileSync(path.join(destination, 'skills/extra-skill/SKILL.md'), '---\nname: [invalid\n---\n', { flag: 'w' }); + } + const pending = Promise.resolve(session.run(plannedInvocation(j, s, roots))).then(value => ({ value }), error => ({ error })); + let cancellationError; + if (s.event) { try { await session.cancel({ id: s.id, event: s.event }); } catch (error) { cancellationError = error; } } + const outcome = await pending; + if (cancellationError || outcome.error) throw new AggregateError([cancellationError, outcome.error].filter(Boolean), 'observed command/cancellation failed'); + const returned = outcome.value; fields(returned, ['row', 'stdout', 'stderr'], 'observed run result'); + const row = returned.row; for (const k of ['stdout', 'stderr']) { textValue(returned[k]); agree(row[k], { size: Buffer.byteLength(returned[k]), sha256: c.digest(Buffer.from(returned[k])) }, 'observed raw output'); } + agree(row.before, before, 'observed before state matches original live roots'); agree(row.after, actualState(j, roots, s), 'observed after state matches original live roots'); + if (s.kind === 'literal-argv') { + const root = path.join(scopePaths(j, s, roots).cwd, 'literal project ü'), bytes = c.readFile(path.join(root, 'plugin.json'), LIMIT); + const manifest = outputJSON(bytes.toString('utf8')); + agree(manifest.description, LITERAL_DESCRIPTION, 'literal argv bytes/cwd effects through real shim'); + agree(row.literal, { root, description: manifest.description, manifest_sha256: c.digest(bytes) }, 'observed literal effect matches original bytes'); + } + if (s.command !== null) { + const core = commandContract(j.cell)[s.command]; commands[s.command] = { product: core.product, id: core.id, argv: core.argv, cwd: row.cwd, + status: row.status, signal: row.signal, stdout: returned.stdout, stderr: returned.stderr }; + } + verifyObservedRow(row, s, j, roots, commands); observed.set(s.id, row); + } + const session = await api['public-process-observation'].openPublicObservation({ cell: r.cell, tools: r.tools, roots }); + try { + for (const method of ['finish', 'run', 'cancel']) assert.equal(typeof session?.[method], 'function', `PUBLIC_FACADE_REQUIRED:public-process-observation.js#session.${method}`); + for (let i = 0; i < all.length;) { + const s = all[i], group = [s]; i++; + if (s.group) while (i < all.length && all[i].group === s.group) group.push(all[i++]); + recheck(); + const before = group.map(s => actualState(j, roots, s)); + // Admit the entire group before launching any member; no source scan or + // synchronous cache walk serializes its four actual requests. + const outcomes = await Promise.allSettled(group.map((s, i) => run(s, before[i]))); + const errors = outcomes.filter(x => x.status === 'rejected').map(x => x.reason); + try { recheck(); } catch (error) { errors.push(error); } + if (errors.length) throw new AggregateError(errors, 'C3 fixed scenario failed'); + } + } catch (e) { primary = e; } + finally { try { if (typeof session?.finish === 'function') terminal = await session.finish(); } catch (e) { finalError = e; } } + if (primary || finalError) { + fs.writeFileSync(path.join(roots.evidence, 'failure.json'), c.encode({ primary: failureText(primary), finalization: failureText(finalError) }), { flag: 'wx', mode: 0o600 }); + throw new AggregateError([primary, finalError].filter(Boolean), 'C3 journey incomplete; no J'); + } + fields(terminal, ['finalization', 'assessment', 'readbacks'], 'observation terminal evidence'); + const evidence = { 'commands.json': commands, + 'projects.json': Object.fromEntries(cell(r.cell).products.map(p => [p, require('./packed-installer-bridge').snapshot(j.projects[p])])), + 'npm-lifecycle.json': { schema: 'authoring-public-npm-lifecycle/v1', cell: r.cell, rows: scenarios.npm.map(s => observed.get(s.id)) }, + 'cache-process.json': { schema: 'authoring-public-cache-process/v1', cell: r.cell, rows: scenarios.cache.map(s => observed.get(s.id)), finalization: terminal.finalization }, + 'installer.json': { schema: 'authoring-public-installer/v1', cell: r.cell, rows: scenarios.installer.map(s => observed.get(s.id)), assessment: terminal.assessment, readbacks: terminal.readbacks } }; + verifyJourney({ record: j, evidence }); recheck(); + const late = admitProducerInputs(r, api['public-authoring-custody']); agree(late.stageBytes, intake.stageBytes, 'late authenticated S'); agree(late.inputBytes, intake.inputBytes, 'late authenticated I'); + agree(late.retained.map(({ kind, relative, sha256, mode }) => ({ kind, relative, sha256, mode })), intake.retained.map(({ kind, relative, sha256, mode }) => ({ kind, relative, sha256, mode })), 'late complete custody multiset'); recheck(); + for (const name of EVIDENCE) { + const files = evidenceFiles(name, evidence[name]); + for (const [file, bytes] of Object.entries(files)) fs.writeFileSync(path.join(roots.evidence, file), bytes, { flag: 'wx', mode: 0o600 }); + const bytes = files[name]; j.evidence.push({ path: name, size: bytes.length, sha256: c.digest(bytes) }); + } + verifySidecars(evidence, roots.evidence); recheck(); + const bytes = encodeJourney(j, intake.inputBytes, intake.stageBytes); + const admission = { schema: 'authoring-public-local-inputs/v1', selected: r.selected, workflow_sha: r.workflow_sha, input_file: path.join(roots.input, contract.INPUT_FILE), stage: r.stage, + repo: r.repo, work_parent: r.work_parent, stage_root: roots.stage, input_root: roots.input, journey_root: roots.evidence, fixture_root: roots.projects, cell: r.cell, tools: r.tools, producer: r.producer }; + const admissionFile = path.join(roots.admission, 'local-inputs.json'), admissionBytes = c.encode(admission); + fs.writeFileSync(admissionFile, admissionBytes, { flag: 'wx', mode: 0o600 }); recheck(); + fs.writeFileSync(path.join(roots.evidence, 'public-journey.json'), bytes, { flag: 'wx', mode: 0o600 }); + return { record: j, request: { intake: INTAKE, expectedCommit: r.workflow_sha, journey: path.join(roots.evidence, 'public-journey.json'), + journeySha256: c.digest(bytes), admission: admissionFile, admissionSha256: c.digest(admissionBytes), fixtureRoot: roots.projects } }; + } catch (error) { + const diagnostic = path.join(fs.existsSync(roots.evidence) ? roots.evidence : r.output, 'failure.json'); + if (!fs.existsSync(diagnostic)) { + try { fs.writeFileSync(diagnostic, c.encode({ primary: failureText(error), finalization: null }), { flag: 'wx', mode: 0o600 }); } + catch (diagnosticError) { throw new AggregateError([error, diagnosticError], 'C3 incomplete; failure receipt could not be written'); } + } + throw error; + } +} + +function readJourney(value) { + const r = request(value), admission = bounded(pin(r.admission, r.admissionSha256), LIMIT); + requireFacades(admission.cell); + const provision = require('./public-authoring-tools'), selected = cell(admission.cell); + agree(provision.requireController(selected.target), process.execPath, 'trusted local controller'); provision.requireCellTools(selected.key); + const frozen = { ...provision.readProvisioning(), key: selected.key }, before = sourceSeal(admission.repo, r.expectedCommit, frozen); + const local = readJourneyInputs(value); verifyJourney(local); verifySidecars(local.evidence, path.dirname(value.journey)); + provision.requireCellTools(selected.key); agree(sourceSeal(admission.repo, r.expectedCommit, frozen), before, 'late source closure'); return local; +} function readAcceptance() { throw new Error("C3b required: completed remote E reader is closed; local J and fixture success are not E"); } function main(args) { + if (args.length === 2 && args[0] === "--produce-journey") return produceJourney(fileJSON(args[1])); assert.ok(args.length === 2 && args[0] === "--read-local-inputs", "C3a supports only --read-local-inputs REQUEST; public execution and E are closed"); const requestValue = fileJSON(args[1]), result = readJourneyInputs(requestValue); return { scope: "authenticated-input-custody-only", cell: result.record.cell, source: result.identity.commit, journey_sha256: requestValue.journeySha256, release_eligible: false, platform_acceptance: false, attested: false }; } // No producer or completed-E CLI can return a success-shaped placeholder. -module.exports = { matrix, commandContract, encodeJourney, decodeJourney, readJourneyInputs, verifyJourney, readJourney, +module.exports = { generatedFiles, generatedTreeIdentity, evidenceFiles, expandEvidence, scenarioContract, plannedInvocation, rootsFor, requireFacades, verifyNpmLifecycle, verifyCacheProcess, verifyResults, produceJourney, + expectedCachePath, PROFILES, SURFACE, clientFacts, componentFacts, LITERAL_DESCRIPTION, outputJSON, matrix, commandContract, encodeJourney, decodeJourney, readJourneyInputs, verifyJourney, readJourney, readAcceptance, request, fileJSON, disjoint, main, LIMIT, SCHEMA, MATRIX_SCHEMA, INTAKE, WORKFLOW, MISSING }; if (require.main === module) { - try { process.stdout.write(c.encode(main(process.argv.slice(2)))); } - catch (error) { process.stderr.write(`C3 local inputs: ${error.message}\n`); process.exitCode = 1; } + Promise.resolve().then(() => main(process.argv.slice(2))).then(result => process.stdout.write(c.encode(result))).catch(error => { process.stderr.write(`C3 public journey: ${error.message}\n`); process.exitCode = 1; }); } diff --git a/npm/agentplugins/test/public-authoring-acceptance.test.js b/npm/agentplugins/test/public-authoring-acceptance.test.js index fa5cf84d..967c6376 100644 --- a/npm/agentplugins/test/public-authoring-acceptance.test.js +++ b/npm/agentplugins/test/public-authoring-acceptance.test.js @@ -61,9 +61,11 @@ function fixture(t) { for (const p of c.PRODUCTS) { const parent = projects[p] = path.join(fixtureRoot, `${p} projects ü`); fs.mkdirSync(parent); for (const lane of bridge.LANES) { - const dir = path.join(parent, lane); fs.mkdirSync(dir); fs.mkdirSync(path.join(dir, "empty")); - fs.mkdirSync(path.join(dir, "skills/extra-skill"), { recursive: true }); - write(path.join(dir, "plugin.json"), { name: lane }); write(path.join(dir, "skills/extra-skill/SKILL.md"), Buffer.from("SYNTHETIC")); + const dir = path.join(parent, lane); fs.mkdirSync(dir, { mode: 0o755 }); + for (const [name, bytes] of Object.entries(a.generatedFiles(lane))) { + const file = path.join(dir, name); fs.mkdirSync(path.dirname(file), { recursive: true, mode: 0o755 }); + fs.writeFileSync(file, bytes, { mode: 0o644 }); + } } } const j = { schema: a.SCHEMA, status: "completed", identity: id, authoring_mode: ic.MODE, asset_scope: ic.SCOPE, @@ -87,7 +89,7 @@ function fixture(t) { const evidence = { "commands.json": rows, "projects.json": Object.fromEntries(c.PRODUCTS.map(p => [p, bridge.snapshot(projects[p])])), "npm-lifecycle.json": { synthetic: true }, "cache-process.json": { synthetic: true }, "installer.json": { synthetic: true } }; const save = () => { - j.evidence = Object.entries(evidence).map(([name, value]) => { const file = path.join(journeyRoot, name); write(file, value); return { path: name, size: fs.statSync(file).size, sha256: hash(file) }; }); + j.evidence = Object.entries(evidence).map(([name, value]) => { const file = path.join(journeyRoot, name); for (const [relative, bytes] of Object.entries(a.evidenceFiles(name, value))) { const target = path.join(journeyRoot, relative); fs.mkdirSync(path.dirname(target), { recursive: true }); write(target, bytes); } return { path: name, size: fs.statSync(file).size, sha256: hash(file) }; }); write(path.join(journeyRoot, "public-journey.json"), a.encodeJourney(j, inputBytes, stageBytes)); }; save(); @@ -111,6 +113,128 @@ function withReaders(t, f, run) { require.cache[name].exports = { ...original, readInputs(options) { assert.deepEqual(options.artifact, f.j.native_inputs.artifact); return f.inputResult; } }; try { return run(); } finally { require.cache[name].exports = original; t.mock.restoreAll(); } } +// Every value below is explicitly SYNTHETIC. No pack, native process, scanner, +// custody or observer implementation is executed by these semantic fixtures. +function semanticFixture(f) { + const j = f.j, commands = a.commandContract(j.cell), roots = a.rootsFor(f.root, j.cell); + const snapshots = f.evidence['projects.json']; + const assessment = status => ({ status, finding_ids: [] }); + const rows = commands.map(w => { + const productVersion = w.id === 'product-version' && w.product === 'agentplugins'; + const args = w.argv.slice(w.product === 'agentplugins' && w.author ? 1 : 0); + const operation = w.id === 'retired-v1' ? 'author' : args[0] === '--help' ? 'author' : `author.${args[0]}${args[0] === 'skills' ? '.' + args[1] : ''}`; + const mutation = ['author.init', 'author.skills.init'].includes(operation), committed = /\/(init|extra-skill)$/.test(w.id); + const project = w.lane && !w.id.endsWith('/existing') && w.id !== 'installer-flag' && !w.id.startsWith('installer/'); + const malformed = w.id === 'malformed-skill'; + const data = Object.fromEntries(['compatibility', 'toolchain', 'loadability', 'normative_conformance', 'host_safety', 'authoring_readiness', 'release_policy', 'runtime_evidence'].map(k => [k, assessment('not_evaluated')])); + Object.assign(data, { schema: 'agentplugins-authoring-report/v1', engine: 'standard-first-slice/1', revision: j.identity.commit, command: operation, + mode: mutation ? 'local_mutation' : 'read', identity: { scope_algorithm: '', read_profile: '', tree_exclusions: null }, + coverage: { components_requested: !!project, skills_enumerated: !!project, inventory_complete: !!project, tree_complete: !!project, + plugin: project ? 'pass' : 'not_evaluated', mcp: project && w.lane !== 'skill' ? 'pass' : 'not_evaluated', skills: project ? 'pass' : 'not_evaluated', filesystem: project ? 'pass' : 'not_evaluated', facts_complete: !!project }, + profiles: project ? a.PROFILES : [], schema_ids: project ? a.PROFILES.slice(w.lane === 'skill' ? 2 : 2, w.lane === 'skill' ? 3 : 4).map(x => x.id).sort() : [], + findings: [], components: project ? a.componentFacts(w.lane, !w.id.endsWith('/init'), malformed) : [], checks: [], committed, + affected_paths: committed ? ['plugin.json'] : [], authoring_schema_version: 1, engine_version: 'standard-first-slice/1', + requested: { operation, mode: mutation ? 'local_mutation' : 'read' }, effects: { attempted: !!project, committed }, next_actions: [] }); + if (project) { + const manifest = snapshots[w.product].entries.find(x => x.path === `${w.lane}/plugin.json`); + data.identity = { scope_algorithm: 'agentplugins-captured-input-sha256-v1', scope_digest: 'sha256:' + H(w.lane), tree_algorithm: 'agentplugins-tree-sha256-v1', + tree_digest: a.generatedTreeIdentity(snapshots[w.product].entries, w.lane, j.cell), manifest_digest: 'sha256:' + manifest.sha256, + read_profile: `packageview-local-${a.matrix.find(c => c.key === j.cell).target.split('-')[0]}-v1`, tree_exclusions: ['root .git', 'root non-directory .plugin-kit-ai.lock'] }; + for (const k of ['loadability', 'normative_conformance', 'host_safety', 'authoring_readiness']) data[k] = assessment(malformed && ['normative_conformance', 'authoring_readiness'].includes(k) ? 'fail' : 'pass'); + data.inspection = { name: w.lane, version: '0.1.0', schema: a.PROFILES[2].id, components: data.components.map(x => + ({ id: x.id, type: x.type, name: x.id === 'sha256:' + H('skill:extra-skill') ? 'extra-skill' : w.lane })) }; + } + if (w.id.endsWith('/doctor')) data.toolchain = assessment(w.lane === 'skill' ? 'pass' : 'not_evaluated'); + if (w.id.endsWith('/compat')) { + data.compatibility = assessment('pass'); data.clients = ['claude', 'codex'].map((client_id, i) => { + const counts = { skill: 0, mcp_server: 0 }; + return { client_id, capabilities: a.clientFacts().find(c => c.client_id === client_id), components: data.components.map(x => ({ kind: x.type === 'skill' ? 'skill' : 'mcp_server' })) + .sort((a, b) => a.kind.localeCompare(b.kind)).map(x => ({ ...x, index: ++counts[x.kind], support: 'projected' })), + limitations: ['static_adapter_support_only', 'installation_not_checked', 'authentication_not_checked', 'runtime_not_checked', 'client_version_not_checked', 'catalog_publication_not_checked', ...(i ? ['manual_activation_required'] : [])] }; + }); + } + if (w.id.endsWith('/test')) data.checks = [['portable_configuration', 'pass'], ['package_hygiene', 'pass'], ['static_skills', 'pass'], ['static_mcp', w.lane === 'skill' ? 'not_evaluated' : 'pass'], ['runtime', 'not_evaluated']].map(([id, status]) => ({ id, ...assessment(status) })); + if (['author-help', 'capabilities', 'engine-version', 'product-version'].includes(w.id)) data.commands = a.SURFACE; + if (w.id === 'capabilities') data.capabilities = { schemas: a.PROFILES.slice(2, 4).map(({ id, digest }) => ({ id, digest })), profiles: a.PROFILES, + clients: a.clientFacts(), commands: a.SURFACE, evidence_limits: ['static_only', 'no_path_lookup', 'no_executable_version_probe', 'no_runtime_or_oauth_evidence', 'native_files_metadata_only'] }; + if (w.id === 'engine-version' || w.product === 'plugin-kit-ai' && w.id === 'product-version') Object.assign(data, { product: w.product, product_version: j.identity.versions[w.product] }); + const result = { schema_version: 1, command: productVersion ? 'version' : operation, result: w.status ? 'failure' : 'success', data: productVersion ? { version: j.identity.versions.agentplugins } : data }; + return { product: w.product, id: w.id, argv: w.argv, cwd: w.scenario === 'projects' ? j.projects[w.product] : path.join(path.dirname(j.projects[w.product]), `${w.product} malformed-skill ü`), + status: w.status, signal: null, stdout: w.id === 'product-help' ? `SYNTHETIC ${w.product} help `.repeat(10) : JSON.stringify(result), stderr: '' }; + }); + f.evidence['commands.json'] = rows; + const inventory = a.scenarioContract(j.cell), prefixes = new Map(), caches = new Map(), selected = a.matrix.find(x => x.key === j.cell); + const empty = () => Object.fromEntries(selected.products.map(p => [p, null])); + const asset = (p, name) => ({ path: a.expectedCachePath(j, roots, { cache: name, prefix: 'unused', product: p, kind: 'cold' }, p), ...Object.fromEntries(['sha256', 'size'].map(k => [k, j.subjects[p][selected.target].binary[k]])), mode: j.cell.startsWith('windows-') ? 0o666 : 0o755 }); + const pkg = (p, prefix) => ({ tree: H(p + 'tree'), shims: (j.cell.startsWith('windows-') ? ['posix', 'cmd', 'powershell'] : ['posix']).map(kind => ({ kind, + path: path.join(roots.npm, prefix, 'prefix', ...(j.cell.startsWith('windows-') ? [] : ['bin']), p + ({ posix: '', cmd: '.cmd', powershell: '.ps1' }[kind])), + sha256: H(p + 'shim'), mode: j.cell.startsWith('windows-') ? 0o666 : 0o777, target: j.cell.startsWith('windows-') ? null : `../lib/node_modules/${ic.PACKAGES[p]}/bin/${p}.js` })) }); + let projectState = H('initial-projects'); const lastMutation = inventory.cache.filter(s => s.command !== null && /\/(init|extra-skill)$/.test(commands[s.command].id)).at(-1).id; + let next = 0; const groups = new Map(); + const ref = () => ({ path: 'sidecars/synthetic-observation.json', size: 10, sha256: H('SYNTHETIC\n') }); + function observation(s) { + if (!prefixes.has(s.prefix)) prefixes.set(s.prefix, empty()); if (!caches.has(s.cache)) caches.set(s.cache, empty()); + const before = { projects: projectState, prefix: structuredClone(prefixes.get(s.prefix)), cache: structuredClone(caches.get(s.cache)), client: H('client'), state: H('state'), inputs: H('inputs') }; + const after = structuredClone(before), p = s.product; + if (s.command !== null && /\/(init|extra-skill)$/.test(commands[s.command].id)) projectState = s.id === lastMutation ? c.digest(c.encode(snapshots)) : H(s.id); + after.projects = projectState; + const npm = ['install', 'reinstall', 'uninstall'].includes(s.kind); + let acquisitions = 0, commits = 0, launches = npm ? 0 : 1; + if (['install', 'reinstall'].includes(s.kind)) after.prefix[p] = pkg(p, s.prefix); + if (s.kind === 'uninstall') after.prefix[p] = null; + if (s.kind.startsWith('invalid-') || s.kind === 'waiter-cancel') launches = 0; + else if (s.kind !== 'uninstall' && (p === 'plugin-kit-ai' || !npm)) { + if (!before.cache[p] || s.kind === 'repair') acquisitions = commits = 1; + after.cache[p] = asset(p, s.cache); + } + if (s.kind === 'concurrent-cold' && !s.id.endsWith('-0')) acquisitions = commits = 0; + if (s.kind === 'repair') before.cache[p] = { ...before.cache[p], sha256: H('C3 intentional owned cache corruption\n'), size: Buffer.byteLength('C3 intentional owned cache corruption\n') }; + if (s.kind === 'concurrent-cold' || s.kind === 'waiter-cancel') before.cache[p] = null; + const planned = a.plannedInvocation(j, s, roots), core = s.command === null ? null : rows[s.command]; + const stdout = core ? core.stdout : npm || s.kind === 'literal-argv' || s.event || s.kind.startsWith('invalid-') ? 'SYNTHETIC process output' : rows.find(r => r.product === p && r.id === 'product-version').stdout; + const row = { id: s.id, command: s.command, argv: planned.argv, cwd: planned.cwd, env: planned.env, + executable: { path: planned.argv[0], sha256: npm ? j.tools.npm_node.sha256 : H(p + 'shim') }, runtime: npm ? j.tools.npm_node : j.tools.shim_node, + stdout: { size: Buffer.byteLength(stdout), sha256: H(stdout) }, stderr: { size: 0, sha256: H('') }, status: core ? core.status : s.kind.startsWith('invalid-') ? 1 : 0, signal: null, + before, after, observation: ref(), events: !npm ? ['shim', ...(launches ? ['native'] : [])] : [], + acquisitions, commits, downloads: 0, native_launches: launches, postinstall: null, interval: [next, next + 10], + literal: s.kind === 'literal-argv' ? { root: path.join(planned.cwd, 'literal project ü'), description: a.LITERAL_DESCRIPTION, manifest_sha256: H('synthetic literal manifest') } : null }; + if (s.group) { if (!groups.has(s.group)) groups.set(s.group, next); row.interval = [groups.get(s.group), groups.get(s.group) + 10]; } next += 20; + if (s.kind === 'waiter-cancel') row.events.push('cache-waiter'); + if (s.kind === 'waiter-owner') row.events.splice(1, 0, 'lock-owner'); + if (s.kind === 'repair') row.events.splice(1, 0, 'repair-before-launch'); + if (s.kind.startsWith('invalid-')) row.events.push('locator-rejected'); + if (s.event) { row.events.push('cancel-delivered'); row.status = ({ SIGINT: 130, SIGTERM: 143, CTRL_C_EVENT: 130, TerminateProcess: 1 })[s.event]; } + row.events.push('reaped'); + if (['install', 'reinstall'].includes(s.kind) && p === 'plugin-kit-ai') row.postinstall = { argv: [j.tools.npm_node.path, './lib/install.js'], runtime: j.tools.npm_node, acquisitions, commits, observation: ref() }; + prefixes.set(s.prefix, after.prefix); caches.set(s.cache, after.cache); return row; + } + const npm = inventory.npm.map(observation), cache = inventory.cache.map(observation), installer = inventory.installer.map(observation); + // Both peer starts were cold in an overlapping group, with disjoint namespaces. + const peers = cache.filter(r => r.id.endsWith('/peer-overlap')); + for (const row of peers) for (const p of selected.products.filter(x => x !== row.id.split('/')[0])) { row.before.cache[p] = null; row.after.cache[p] = asset(p, 'peer-overlap'); } + f.evidence['npm-lifecycle.json'] = { schema: 'authoring-public-npm-lifecycle/v1', cell: j.cell, rows: npm }; + f.evidence['cache-process.json'] = { schema: 'authoring-public-cache-process/v1', cell: j.cell, rows: cache, finalization: { + rows: [...inventory.npm, ...inventory.cache, ...inventory.installer].map(s => s.id), descendants: [], locks: [], late_errors: [], observation: ref() } }; + f.evidence['installer.json'] = { schema: 'authoring-public-installer/v1', cell: j.cell, rows: installer, assessment: selected.node === 18 ? null : ref(), readbacks: inventory.installer.map(ref) }; + const sidecars = path.join(f.admission.journey_root, 'sidecars'); fs.mkdirSync(sidecars, { recursive: true }); fs.writeFileSync(path.join(sidecars, 'synthetic-observation.json'), 'SYNTHETIC\n'); + f.repin(); return f; +} +function withFacades(t, run) { + const exists = fs.existsSync, Module = require('node:module'), load = Module._load; + const api = { + 'public-authoring-custody': { readPublicInputs() { throw new Error('SYNTHETIC custody not provided'); } }, + 'public-process-observation': { openPublicObservation() { throw new Error('SYNTHETIC session not provided'); }, verifyPublicObservation({ rows, finalization }) { return { rows, finalization }; } }, + 'public-installer-evidence': { requirePublicInstaller() { return undefined; }, verifyPublicInstaller({ assessment, readbacks }) { return { assessment, readbacks }; } } + }; + fs.existsSync = file => Object.keys(api).some(n => file === path.join(repo, 'npm/agentplugins/scripts', n + '.js')) || exists(file); + Module._load = function(name, ...args) { + const key = Object.keys(api).find(n => name === path.join(repo, 'npm/agentplugins/scripts', n + '.js')); + return key ? api[key] : load.call(this, name, ...args); + }; + const restore = () => { fs.existsSync = exists; Module._load = load; }; + try { const result = run(api); if (result && typeof result.then === 'function') return result.finally(restore); restore(); return result; } catch (e) { restore(); throw e; } +} + module.exports = { fixture, withReaders }; if (require.main === module) { test("C3 unit closed schemas and immutable pair bindings", t => { @@ -131,9 +255,9 @@ if (require.main === module) { test("C3 unit stage admission precedes npm and native effects", t => { const f = fixture(t); withReaders(t, f, () => { - const local = a.readJourneyInputs(f.request); assert.equal(local.projects.length, 10); + semanticFixture(f); const local = a.readJourneyInputs(f.request); assert.equal(local.projects.length, 10); assert.equal(s.readStage.mock.callCount(), 1); - assert.throws(() => a.readJourney(f.request), /C3b required/); + assert.throws(() => a.readJourney(f.request), /PUBLIC_FACADE_REQUIRED/); const cache = require.cache[require.resolve("../scripts/authoring-native-inputs")], previous = cache.exports; cache.exports = { ...previous, readInputs() { throw new Error("I verifier unavailable"); } }; try { assert.throws(() => a.readJourneyInputs(f.request), /I verifier unavailable/); } @@ -157,24 +281,94 @@ if (require.main === module) { }); test("C3 unit journey results parity and preservation", t => { const f = fixture(t); withReaders(t, f, () => { - const local = a.readJourneyInputs(f.request); - assert.throws(() => a.verifyJourney(local), /C3b required/); + semanticFixture(f); const local = a.readJourneyInputs(f.request); + assert.throws(() => a.verifyJourney(local), /PUBLIC_FACADE_REQUIRED/); + assert.deepEqual(a.verifyResults(f.j, f.evidence), { fixed_commands: true, pair_parity: true, projects_preserved: true }); + const resultCases = [ + ['schema', d => { d.schema = 'other'; }], ['profile', d => { d.profiles[0].digest = 'sha256:' + H('wrong'); }], + ['read profile', d => { d.identity.read_profile = 'other-host'; }], ['component name', d => { d.inspection.components[0].name = 'wrong'; }], + ['component type', d => { d.inspection.components[0].type = 'wrong'; }], + ['coverage', d => { d.coverage.filesystem = 'not_evaluated'; }], + ['component omitted', d => d.components.pop()], ['readiness', d => { d.authoring_readiness.status = 'not_evaluated'; }], + ['runtime claim', d => { d.runtime_evidence.status = 'pass'; }], ['release claim', d => { d.release_policy.status = 'pass'; }], + ['diagnostic parity', d => { d.next_actions.push({ code: 'different', message: 'different' }); }], + ['unknown nested', d => { d.identity.extra = true; }] + ]; + for (const [name, mutate] of resultCases) { + const evidence = structuredClone(f.evidence), row = evidence['commands.json'].find(r => r.product === 'agentplugins' && r.id === 'skill/inspect'); + const value = JSON.parse(row.stdout); mutate(value.data); row.stdout = JSON.stringify(value); + assert.throws(() => a.verifyResults(f.j, evidence), name); t.diagnostic(name); + } + assert.throws(() => a.outputJSON('{"a":1,"a":2}'), /duplicate/); + withFacades(t, () => assert.equal(a.verifyJourney(local), local)); + for (const [name, mutate] of [ ["drop row", x => x.evidence["commands.json"].pop()], ["arbitrary argv", x => x.evidence["commands.json"][0].argv.push("--extra")], ["cwd", x => { x.evidence["commands.json"][0].cwd = f.root; }], ["signal", x => { x.evidence["commands.json"][0].signal = "SIGTERM"; }], ["false result", x => { x.evidence["commands.json"][0].stdout = "{}"; }]]) { - const bad = structuredClone(local); mutate(bad); assert.throws(() => a.verifyJourney(bad), e => !e.message.includes("C3b required")); t.diagnostic(name); + const bad = structuredClone(local); mutate(bad); assert.throws(() => a.verifyJourney(bad), e => !e.message.includes("PUBLIC_FACADE_REQUIRED")); t.diagnostic(name); } const file = path.join(f.j.projects.agentplugins, "skill/plugin.json"); fs.chmodSync(file, 0o400); assert.throws(() => a.readJourneyInputs(f.request), /original project trees/); }); }); + test('C3 unit fixed generated closure and captured identity', t => { + const f = semanticFixture(fixture(t)); + assert.ok(a.verifyResults(f.j, f.evidence).projects_preserved); + const extraRoot = structuredClone({ 'commands.json': f.evidence['commands.json'], 'projects.json': f.evidence['projects.json'] }); + for (const snapshot of Object.values(extraRoot['projects.json'])) { + snapshot.entries.push({ path: 'unexpected', kind: 'file', mode: 0o644, size: 0, sha256: H('') }); + snapshot.sha256 = c.digest(c.encode(snapshot.entries)); + } + assert.throws(() => a.verifyResults(f.j, extraRoot), /only generated lane entries/); + // Independently rendered scaffold fixtures and ordinary v1 framing pins. + const golden = {"skill": "sha256:383668d78b12a5ad868c895183a6d86b3441c6fedc833371250eecd8e40d9754", "mcp-remote": "sha256:2aeaa18441a1e66f8b31c9beee438f88205c9dd24a4d73dd3fc24ebfce50c005", "mcp-stdio": "sha256:39908919898e31f6bd170623b8f70581aa5604f21910c334466d1fc527028e39", "hybrid-remote": "sha256:66fa98b22663011eb77e8ffbe4b73f056eb94b2706fe9f9a58244d32e0935bdb", "hybrid-stdio": "sha256:1ebd460c3872e672a6378226265f89d861160f29932a0b15ae8e376a3c576f6b"}; + for (const lane of bridge.LANES) { + const entries = f.evidence['projects.json'].agentplugins.entries; + for (const cell of a.matrix) { + const hostEntries = structuredClone(entries); + if (cell.target.startsWith('windows-')) for (const e of hostEntries) e.mode = e.kind === 'directory' ? 0o777 : 0o666; + assert.equal(a.generatedTreeIdentity(hostEntries, lane, cell.key), golden[lane]); + } + for (const entry of entries.filter(e => e.path.startsWith(lane + '/'))) { + for (const mutation of ['missing', 'mode', ...(entry.kind === 'file' ? ['content'] : [])]) { + const bad = structuredClone({ 'commands.json': f.evidence['commands.json'], 'projects.json': f.evidence['projects.json'] }); + for (const snapshot of Object.values(bad['projects.json'])) { + const e = snapshot.entries.find(e => e.path === entry.path); + if (mutation === 'missing') snapshot.entries = snapshot.entries.filter(x => x.path !== e.path); + else if (mutation === 'mode') e.mode ^= 0o100; + else e.sha256 = H('symmetric changed content'); + snapshot.sha256 = c.digest(c.encode(snapshot.entries)); + } + for (const snapshot of Object.values(bad['projects.json'])) + assert.throws(() => a.generatedTreeIdentity(snapshot.entries, lane, f.j.cell), e => e.message.length < 1024, `${lane} ${mutation} ${entry.path}`); + if (entry.path === lane + '/plugin.json') assert.throws(() => a.verifyResults(f.j, bad), e => e.message.length < 1024); + } + } + for (const kind of ['file', 'directory']) { + const bad = structuredClone({ 'commands.json': f.evidence['commands.json'], 'projects.json': f.evidence['projects.json'] }); + for (const snapshot of Object.values(bad['projects.json'])) { + snapshot.entries.push({ path: lane + '/unexpected-empty', mode: kind === 'file' ? 0o644 : 0o755, kind, + ...(kind === 'file' ? { size: 0, sha256: H('') } : {}) }); + snapshot.sha256 = c.digest(c.encode(snapshot.entries)); + } + assert.throws(() => a.verifyResults(f.j, bad), e => e.message.length < 1024); + } + const bad = structuredClone({ 'commands.json': f.evidence['commands.json'], 'projects.json': f.evidence['projects.json'] }); + for (const row of bad['commands.json'].filter(r => r.id.startsWith(lane + '/'))) { + const v = JSON.parse(row.stdout); + if (v.data.identity.tree_digest) v.data.identity.tree_digest = 'sha256:' + H('unbound identical claim'); + row.stdout = JSON.stringify(v); + } + assert.throws(() => a.verifyResults(f.j, bad), /captured generated tree identity/); + } + }); test("C3 unit production installer evidence is independently required", t => { const f = fixture(t); withReaders(t, f, () => { - const local = a.readJourneyInputs(f.request); - assert.throws(() => a.verifyJourney(local), /reviewed public installer result validator and whole-descendant observer/); - assert.throws(() => bridge.publishSeal(f.request, path.join(f.root, "must-not-exist")), /C3b required/); + semanticFixture(f); const local = a.readJourneyInputs(f.request); + assert.throws(() => a.verifyJourney(local), /PUBLIC_FACADE_REQUIRED/); + assert.throws(() => bridge.publishSeal(f.request, path.join(f.root, "must-not-exist")), /PUBLIC_FACADE_REQUIRED/); assert.equal(fs.existsSync(path.join(f.root, "must-not-exist")), false); }); }); @@ -192,11 +386,189 @@ if (require.main === module) { const f = fixture(t); assert.throws(() => a.readAcceptance(f.request), /completed remote E reader is closed/); assert.throws(() => a.request({ ...f.request, expectedCommit: f.request.expectedCommit + "\n" })); for (const extra of [{ authenticated: true }, { completed: true }, { allowPublic: true }]) assert.throws(() => a.request({ ...f.request, ...extra })); - assert.throws(() => a.main(["--produce-journey", f.request.journey]), /only --read-local-inputs/); + assert.throws(() => a.main(["--assemble", f.request.journey]), /only --read-local-inputs/); }); test("C3 unit legacy fixtures cannot qualify authentic acceptance", t => { const f = fixture(t); for (const intake of ["public-fixture/v1", "public-fixture/v2", "private"]) assert.throws(() => a.request({ ...f.request, intake })); for (const schema of ["dual-authoring-public-native/v1", "dual-authoring-public-native/v2", "authoring-public-packed/v1"]) assert.throws(() => a.encodeJourney({ ...f.j, schema }, f.inputBytes, f.stageBytes)); }); + test('C3 unit npm shims postinstall and peer lifecycle contract', async t => { + const f = semanticFixture(fixture(t)), j = f.j, roots = a.rootsFor(f.root, j.cell), original = f.evidence['npm-lifecycle.json']; + assert.deepEqual(a.verifyNpmLifecycle(j, original, roots), { npm_lifecycle: true }); + const cases = [ + ['omitted npm row', x => x.rows.pop()], ['wrong argv', x => x.rows[0].argv.push('--ignore-scripts')], + ['wrong cwd', x => { x.rows[0].cwd = f.root; }], ['extra env', x => { x.rows[0].env.NODE_OPTIONS = '--require evil'; }], + ['wrong npm runtime', x => { x.rows[0].runtime = structuredClone(j.tools.orchestrator_node); x.rows[0].runtime.version = 'v18.0.0'; }], + ['missing postinstall', x => { x.rows.find(r => r.postinstall).postinstall = null; }], + ['postinstall wrong node', x => { x.rows.find(r => r.postinstall).postinstall.runtime.version = 'v18.0.0'; }], + ['retained removed shim', x => { const r = x.rows.find(r => r.id.endsWith('/uninstall')); r.after.prefix.agentplugins = r.before.prefix.agentplugins; }], + ['peer damaged', x => { const r = x.rows.find(r => r.id.includes('/shared-agentplugins/uninstall')); r.after.prefix['plugin-kit-ai'].tree = H('changed'); }], + ['reinstall changed bytes', x => { const r = x.rows.find(r => r.id.endsWith('/reinstall')); r.after.prefix.agentplugins.tree = H('changed'); }], + ['native missing', x => { x.rows.find(r => r.id.endsWith('/probe')).native_launches = 0; }] + ]; + for (const [name, mutate] of cases) { const bad = structuredClone(original); mutate(bad); assert.throws(() => a.verifyNpmLifecycle(j, bad, roots), name); t.diagnostic(name); } + for (const cell of a.matrix) { + const contract = a.scenarioContract(cell.key); assert.ok(Object.isFrozen(contract) && Object.isFrozen(contract.npm)); + assert.equal(contract.npm.length, cell.node === 18 ? 5 : 34); assert.equal(contract.installer.length, cell.node === 18 ? 0 : 18); + const fake = structuredClone(j); fake.cell = cell.key; + if (cell.target.startsWith('windows-')) { + fake.projects = Object.fromEntries(cell.products.map(p => [p, `C:\\C3\\projects\\${p} projects ü`])); + for (const k of ['npm_node', 'shim_node', 'npm']) fake.tools[k].path = `C:\\tools\\${k}.exe`; + const r = a.rootsFor('C:\\C3', cell.key), literal = contract.cache.find(s => s.kind === 'literal-argv'); + const invocation = a.plannedInvocation(fake, literal, r); + assert.match(invocation.argv[0], /powershell\.exe$/); assert.ok(invocation.argv.at(-1).includes('.ps1')); + assert.ok(invocation.argv.at(-1).includes("''single''")); assert.ok(invocation.argv.at(-1).includes('$(literal)')); + const version = contract.cache.find(s => s.command !== null && a.commandContract(cell.key)[s.command].id === 'product-version'); + assert.ok(a.plannedInvocation(fake, version, r).argv.at(-1).includes('.cmd')); + } else { + const installation = a.plannedInvocation(fake, contract.npm[0], roots); + assert.deepEqual(installation.argv.slice(2, -1), ['install', '--global', '--prefix', path.join(roots.npm, contract.npm[0].prefix, 'prefix'), '--offline', '--ignore-scripts=false', '--foreground-scripts', '--no-audit', '--no-fund']); + const literal = a.plannedInvocation(fake, contract.cache.find(s => s.kind === 'literal-argv'), roots); + assert.ok(literal.argv.includes(a.LITERAL_DESCRIPTION)); assert.ok(!literal.argv[0].endsWith('.js')); + } + } + // The real producer must close before any output/npm/native effect when its + // source-frozen provision is absent; no input chooses a callback or command. + const tools = require('../scripts/public-authoring-tools'); let checks = 0; + t.mock.method(tools, 'requireController', () => { checks++; throw new Error('PUBLIC_PROVISIONING_REQUIRED:synthetic'); }); + const before = fs.readdirSync(f.root); + await assert.rejects(a.produceJourney({ command: ['arbitrary'], success() {} }), /PUBLIC_PROVISIONING_REQUIRED/); + assert.equal(checks, 1); assert.deepEqual(fs.readdirSync(f.root), before); t.mock.restoreAll(); + assert.throws(() => a.requireFacades(j.cell), /public-authoring-custody.js#readPublicInputs.*public-process-observation.js#openPublicObservation.*public-installer-evidence.js#requirePublicInstaller/); + // Real producer control flow, with opaque synthetic owner interfaces. No + // npm/native/tool fixture is executed and no synthetic J can be emitted. + const request = { schema: 'authoring-public-produce/v1', selected: f.admission.selected, workflow_sha: j.identity.commit, + stage: j.stage, input_file: f.admission.input_file, repo, work_parent: f.admission.work_parent, + output: path.join(f.root, 'producer-control'), cell: j.cell, tools: j.tools, producer: j.producer }; + const frozen = { ...j.tools, npm: { ...j.tools.npm, closure: { root: path.dirname(j.tools.npm.path) } }, mod_cache: null }; + const history = []; + t.mock.method(tools, 'requireController', () => process.execPath); + t.mock.method(tools, 'requireCellTools', () => frozen); + t.mock.method(tools, 'readProvisioning', () => ({ controllers: { 'linux-amd64': { git: j.tools.go } } })); + t.mock.method(require('node:child_process'), 'execFileSync', (file, args) => { + assert.equal(file, j.tools.go.path); history.push('source:' + args[0]); + if (args[0] === 'rev-parse') return Buffer.from(j.identity.commit); + if (args[0] === 'status') return Buffer.alloc(0); + assert.deepEqual(args, ['ls-files', '-z']); return Buffer.from('npm/agentplugins/scripts/public-authoring-acceptance.js\0'); + }); + await withFacades(t, async api => { + api['public-authoring-custody'].readPublicInputs = () => { history.push('custody'); throw new Error('SYNTHETIC custody denial'); }; + await assert.rejects(a.produceJourney(request), /custody denial/); + assert.equal(fs.existsSync(request.output), false); + assert.ok(history.indexOf('source:ls-files') < history.indexOf('custody')); + const subjects = [f.inputResult.subjects[0], ...f.inputResult.subjects.slice(1, 7)]; + for (const product of c.PRODUCTS) for (const target of c.TARGETS) { + const file = path.join(f.inputResult.root, j.subjects[product][target].file); + write(file, Buffer.from((product === 'agentplugins' ? '' : 'outer') + product + target)); + subjects.push({ file, sha256: hash(file) }); + } + api['public-authoring-custody'].readPublicInputs = () => ({ stage: f.stageResult, input: { ...f.inputResult, subjects } }); + api['public-installer-evidence'].requirePublicInstaller = () => { throw new Error('SYNTHETIC installer unavailable'); }; + await assert.rejects(a.produceJourney(request), /installer unavailable/); + assert.equal(fs.existsSync(request.output), false); + api['public-installer-evidence'].requirePublicInstaller = () => history.push('installer-ready'); + let runs = 0, finishes = 0; + api['public-process-observation'].openPublicObservation = ({ roots }) => { + history.push('observation-open'); + assert.equal(fs.readFileSync(path.join(roots.npm, 'alone-agentplugins/user.npmrc'), 'utf8'), ''); + return { + run(invocation) { runs++; assert.deepEqual(invocation, a.plannedInvocation(j, a.scenarioContract(j.cell).npm[0], roots)); throw new Error('SYNTHETIC primary run failure'); }, + cancel() { assert.fail('no cancellation scenario reached'); }, + finish() { finishes++; throw new Error('SYNTHETIC late finalization failure'); } + }; + }; + await assert.rejects(a.produceJourney(request), /journey incomplete/); + assert.equal(runs, 1); assert.equal(finishes, 1); + assert.ok(history.indexOf('installer-ready') < history.indexOf('observation-open')); + const failure = JSON.parse(fs.readFileSync(path.join(request.output, 'evidence/failure.json'))); + assert.match(failure.primary, /C3 fixed scenario failed/); assert.match(failure.primary, /primary run failure/); assert.match(failure.finalization, /late finalization failure/); + assert.equal(fs.existsSync(path.join(request.output, 'evidence/public-journey.json')), false); + for (const failFinish of [false, true]) { + request.output = path.join(f.root, `malformed-observer-${failFinish}`); + let finished = 0, ran = 0; + api['public-process-observation'].openPublicObservation = () => ({ + run() { ran++; }, + finish() { finished++; if (failFinish) throw new Error('SYNTHETIC malformed finalizer failure'); } + }); + await assert.rejects(a.produceJourney(request), /journey incomplete/); + assert.equal(finished, 1); assert.equal(ran, 0); + const receipt = JSON.parse(fs.readFileSync(path.join(request.output, 'evidence/failure.json'))); + assert.match(receipt.primary, /session.cancel/); + if (failFinish) assert.match(receipt.finalization, /malformed finalizer failure/); + else assert.equal(receipt.finalization, null); + assert.equal(fs.existsSync(path.join(request.output, 'evidence/public-journey.json')), false); + } + for (const session of [null, { run() {}, cancel() {} }]) { + request.output = path.join(f.root, `no-finalizer-${session === null}`); + api['public-process-observation'].openPublicObservation = () => session; + await assert.rejects(a.produceJourney(request), /journey incomplete/); + const receipt = JSON.parse(fs.readFileSync(path.join(request.output, 'evidence/failure.json'))); + assert.match(receipt.primary, /session.finish/); assert.equal(receipt.finalization, null); + assert.equal(fs.existsSync(path.join(request.output, 'evidence/public-journey.json')), false); + } + request.output = path.join(f.root, 'observer-open-failure'); + api['public-process-observation'].openPublicObservation = () => { throw new Error('SYNTHETIC observation unavailable'); }; + await assert.rejects(a.produceJourney(request), /observation unavailable/); + assert.match(JSON.parse(fs.readFileSync(path.join(request.output, 'evidence/failure.json'))).primary, /observation unavailable/); + assert.equal(fs.existsSync(path.join(request.output, 'evidence/public-journey.json')), false); + }); + t.mock.restoreAll(); + + }); + test('C3 unit cache process failure and cancellation ordering', t => { + const f = semanticFixture(fixture(t)), j = f.j, roots = a.rootsFor(f.root, j.cell), original = f.evidence['cache-process.json']; + const verify = value => a.verifyCacheProcess(j, value, roots, f.evidence['commands.json']); + assert.deepEqual(verify(original), { cache_process: true, children_reaped: true }); + // Transport regression: full long-path observations exceed a 1MiB record, + // without dropping rows or relaxing record/output/shard/aggregate limits. + const transport = a.evidenceFiles('cache-process.json', original); + assert.ok(transport['cache-process.json'].length <= a.LIMIT); + assert.deepEqual(a.expandEvidence('cache-process.json', JSON.parse(transport['cache-process.json']), f.admission.journey_root), original); + const padded = { ...original, rows: original.rows.map(r => ({ ...r, diagnostic: 'x'.repeat(130000) })) }; + const large = a.evidenceFiles('cache-process.json', padded); + assert.ok(Object.keys(large).filter(n => n.startsWith('sidecars/')).length > 1); + for (const [name, bytes] of Object.entries(large)) assert.ok(bytes.length <= (name.startsWith('sidecars/') ? 16 * a.LIMIT : a.LIMIT)); + const shardRoot = path.join(f.root, 'transport'); fs.mkdirSync(path.join(shardRoot, 'sidecars'), { recursive: true }); + for (const [name, bytes] of Object.entries(large)) write(path.join(shardRoot, name), bytes); + assert.ok(require('node:util').isDeepStrictEqual(a.expandEvidence('cache-process.json', JSON.parse(large['cache-process.json']), shardRoot), padded)); + assert.throws(() => a.evidenceFiles('cache-process.json', { ...original, rows: [{ data: 'x'.repeat(a.LIMIT) }] }), /1MiB process record/); + const index = JSON.parse(transport['cache-process.json']); + assert.throws(() => a.expandEvidence('cache-process.json', index, f.admission.journey_root, { size: 128 * a.LIMIT }), /128MiB/); + const badIndex = structuredClone(index); badIndex.rows.shards[0].sha256 = H('changed'); + assert.throws(() => a.expandEvidence('cache-process.json', badIndex, f.admission.journey_root), /pin/); + const find = (x, kind) => x.rows.find(r => r.id.endsWith('/' + kind)); + const cases = [ + ['warm reacquisition', x => { find(x, 'warm').acquisitions++; }], + ['warm wrong bytes', x => { find(x, 'warm').after.cache.agentplugins.sha256 = H('wrong'); }], + ['invalid cold launches', x => { find(x, 'invalid-cold').native_launches = 1; }], + ['invalid warm fallback', x => { find(x, 'invalid-warm').downloads = 1; }], + ['uncorrupted repair input', x => { find(x, 'repair').before.cache.agentplugins = structuredClone(find(x, 'repair').after.cache.agentplugins); }], + ['unchecked repair', x => { const r = find(x, 'repair'); r.events = r.events.filter(x => x !== 'repair-before-launch'); }], + ['repair wrong mode', x => { find(x, 'repair').after.cache.agentplugins.mode = 0o644; }], + ['nonoverlapping concurrency', x => { find(x, 'concurrent-cold-3').interval = [100000, 100001]; }], + ['two concurrent commits', x => { find(x, 'concurrent-cold-2').commits++; }], + ['missing fourth request', x => { x.rows.splice(x.rows.findIndex(r => r.id.endsWith('/concurrent-cold-3')), 1); }], + ['signal before boundary', x => { find(x, 'SIGINT').events = ['cancel-delivered', 'reaped']; }], + ['cancel before native', x => { find(x, 'SIGINT').events = ['shim', 'cancel-delivered', 'native', 'reaped']; }], + ['early reap', x => { find(x, 'SIGINT').events = ['shim', 'reaped', 'native', 'cancel-delivered']; }], + ['waiter native launch', x => { find(x, 'waiter-cancel').native_launches = 1; }], + ['wrong cancel exit', x => { find(x, 'SIGTERM').status = 0; }], + ['not a cache waiter', x => { const r = find(x, 'waiter-cancel'); r.events = r.events.filter(x => x !== 'cache-waiter'); }], + ['leaked descendant', x => x.finalization.descendants.push(42)], ['owned lock leak', x => x.finalization.locks.push('lock')], + ['late observer error', x => x.finalization.late_errors.push('denied')], ['omitted final row', x => x.finalization.rows.pop()], + ['oversized observation', x => { x.rows[0].observation.size = 16 * a.LIMIT + 1; }], + ['literal effect missing', x => { find(x, 'literal-argv').literal = null; }], + ['literal expansion', x => { find(x, 'literal-argv').literal.description = 'expanded shell values'; }], + ['literal wrong cwd effect', x => { find(x, 'literal-argv').literal.root = f.root; }], + ['direct-bin substitution', x => { x.rows[0].argv = [j.tools.shim_node.path, 'bin/agentplugins.js']; }] + ]; + for (const [name, mutate] of cases) { const bad = structuredClone(original); mutate(bad); assert.throws(() => verify(bad), name); t.diagnostic(name); } + withFacades(t, api => { + assert.equal(a.verifyJourney({ record: j, evidence: f.evidence }).record, j); + api['public-process-observation'].verifyPublicObservation = () => true; + assert.throws(() => a.verifyJourney({ record: j, evidence: f.evidence }), /never boolean success/); + }); + }); + }