Sign In

attepack

Package Overview
Dependencies
Maintainers
1
Versions
3
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

attepack - npm Package Compare versions

Comparing version
0.1.1
to
0.2.1
+55
fixtures/evidence-protocol/guidance-impact.v1.json
{
"type": "skillpress.guidance_impact.v1",
"schema_version": 1,
"ok": true,
"producer": {
"id": "skillpress"
},
"repository": {
"id": "attebury/skillpress"
},
"candidate": {
"base_sha": "1111111111111111111111111111111111111111",
"head_sha": "2222222222222222222222222222222222222222"
},
"capability_contracts": [
{
"capability_id": "skillpress.capability_contracts",
"digest": "sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
}
],
"source_artifacts": [
{
"artifact_id": "skillpress-repo-skill",
"digest": "sha256:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"
}
],
"scenario_results": [
{
"id": "candidate-impact-no-drift",
"status": "pass"
}
],
"authority_role": "advisory",
"authority": {
"role": "advisory",
"owns": [
"guidance_impact"
],
"not_authority_for": [
"merge",
"release",
"closeout",
"software_correctness"
]
},
"limitations": [
{
"code": "semantic_review_not_included"
}
],
"evidence_refs": [
"evidence/skillpress-impact.json"
],
"evidence_digest": "sha256:7b71d544d3ba96e3b771dca6637dd49c9ddca7a348de37067ed1973a8a13e81d"
}
{
"type": "verigram.semantic_guidance_review.v1",
"schema_version": 1,
"ok": true,
"producer": {
"id": "verigram"
},
"repository": {
"id": "attebury/skillpress"
},
"candidate": {
"base_sha": "1111111111111111111111111111111111111111",
"head_sha": "2222222222222222222222222222222222222222"
},
"parent_evidence": [
{
"type": "skillpress.guidance_impact.v1",
"digest": "sha256:7b71d544d3ba96e3b771dca6637dd49c9ddca7a348de37067ed1973a8a13e81d"
}
],
"capability_contracts": [
{
"capability_id": "skillpress.capability_contracts",
"digest": "sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
}
],
"source_artifacts": [
{
"artifact_id": "skillpress-repo-skill",
"digest": "sha256:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"
}
],
"reviewer": {
"id": "reviewer-1"
},
"disposition": "approved",
"authority_role": "validator",
"authority": {
"role": "validator",
"owns": [
"semantic_guidance_review"
],
"not_authority_for": [
"merge",
"release",
"closeout",
"software_correctness"
]
},
"limitations": [
{
"code": "implementation_correctness_not_reviewed"
}
],
"evidence_refs": [
"evidence/verigram-semantic-review.json"
],
"evidence_digest": "sha256:c376dc709796cb5600428354e21018dd67d6deaf3da6037c481bd90eebc55763"
}
{
"type": "skillpress.sync_receipt.v1",
"schema_version": 1,
"ok": true,
"producer": {
"id": "skillpress"
},
"repository": {
"id": "attebury/skillpress"
},
"merged_source": {
"commit_sha": "3333333333333333333333333333333333333333"
},
"parent_evidence": [
{
"type": "skillpress.guidance_impact.v1",
"digest": "sha256:7b71d544d3ba96e3b771dca6637dd49c9ddca7a348de37067ed1973a8a13e81d"
}
],
"source_artifacts": [
{
"artifact_id": "skillpress-repo-skill",
"digest": "sha256:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"
}
],
"target_sets": {
"configured": [
{
"id": "codex",
"digest": "sha256:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc"
},
{
"id": "agents",
"digest": "sha256:dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd"
}
],
"attempted": [
{
"id": "codex",
"digest": "sha256:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc"
},
{
"id": "agents",
"digest": "sha256:dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd"
}
],
"verified": [
{
"id": "codex",
"digest": "sha256:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc"
},
{
"id": "agents",
"digest": "sha256:dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd"
}
]
},
"authority_role": "advisory",
"authority": {
"role": "advisory",
"owns": [
"provider_readback"
],
"not_authority_for": [
"merge",
"release",
"closeout",
"software_correctness"
]
},
"limitations": [
{
"code": "provider_semantics_not_reviewed"
}
],
"evidence_refs": [
"evidence/skillpress-sync.json"
],
"evidence_digest": "sha256:42bffceae0ae7974fa4b163de131a4f89004200e76beaecbe9b081b0b30e3bc7"
}
/**
* Fail-closed CLI JSON emit helpers.
*
* A common CLI failure mode: `console.log(JSON.stringify(packet)); process.exit(0)`
* races the async flush of stdout when stdout is a pipe (not a TTY). The
* process can exit 0 while emitting truncated, unparseable JSON, silently
* dropping trailing fields such as `validity` or `not_authority_for`. Exit
* status alone is then insufficient evidence that the emitted packet is
* intact.
*
* `emitJsonPacket` avoids the race by only advancing past the write once the
* stream's write callback confirms the data was handed off, and only ever
* calling a hard `process.exit` from inside that callback (never before). By
* default it does not call `process.exit` at all: it sets `process.exitCode`
* and resolves, letting Node exit naturally once the event loop drains,
* which is the safest way to guarantee the write is flushed first.
*/
export const CLI_EMIT_ERROR_CODES = Object.freeze({
INVALID_ARGS: 'invalid_args',
PACKET_NOT_SERIALIZABLE: 'packet_not_serializable',
PACKET_TOO_LARGE: 'packet_too_large',
WRITE_FAILED: 'write_failed',
});
function cliEmitError(code, message, details = null) {
const error = new Error(message);
error.code = code;
if (details && typeof details === 'object') {
error.details = details;
}
return error;
}
/**
* Serialize `packet` as a single JSON line and write it to `stream`,
* resolving only after the write is confirmed flushed by the stream.
*
* @param {*} packet - JSON-serializable value to emit.
* @param {object} [options]
* @param {number} [options.exitCode=0] - Value assigned to `process.exitCode`
* once the write is confirmed. Assigning `process.exitCode` (rather than
* forcing `process.exit`) lets Node exit naturally with this code.
* @param {number|null} [options.maxBytes=null] - Optional maximum byte length
* (UTF-8, including the trailing newline) for the serialized packet. When
* exceeded, throws synchronously with code `packet_too_large` before any
* write is attempted.
* @param {NodeJS.WritableStream} [options.stream=process.stdout] - Stream to
* write to; injectable for tests.
* @param {((exitCode: number) => void)|null} [options.exit=null] - Optional
* hard-exit hook (e.g. `process.exit`), invoked only after the write
* callback confirms the flush. When omitted, no forced exit is triggered.
* @returns {Promise<{ok: true, bytes_written: number, exit_code: number}>}
*/
export function emitJsonPacket(packet, options = {}) {
const {
exitCode = 0,
maxBytes = null,
stream = process.stdout,
exit = null,
} = options;
if (!Number.isInteger(exitCode) || exitCode < 0) {
throw cliEmitError(
CLI_EMIT_ERROR_CODES.INVALID_ARGS,
'emitJsonPacket requires a non-negative integer "exitCode" option',
);
}
if (maxBytes !== null && (!Number.isInteger(maxBytes) || maxBytes < 0)) {
throw cliEmitError(
CLI_EMIT_ERROR_CODES.INVALID_ARGS,
'emitJsonPacket "maxBytes" option must be a non-negative integer or null',
);
}
if (exit !== null && typeof exit !== 'function') {
throw cliEmitError(
CLI_EMIT_ERROR_CODES.INVALID_ARGS,
'emitJsonPacket "exit" option must be a function or null',
);
}
if (!stream || typeof stream.write !== 'function') {
throw cliEmitError(
CLI_EMIT_ERROR_CODES.INVALID_ARGS,
'emitJsonPacket "stream" option must be a writable stream',
);
}
let serialized;
try {
serialized = JSON.stringify(packet);
} catch (cause) {
throw cliEmitError(
CLI_EMIT_ERROR_CODES.PACKET_NOT_SERIALIZABLE,
`emitJsonPacket requires a JSON-serializable packet: ${cause.message}`,
);
}
if (serialized === undefined) {
throw cliEmitError(
CLI_EMIT_ERROR_CODES.PACKET_NOT_SERIALIZABLE,
'emitJsonPacket requires a JSON-serializable packet (received a value that serializes to undefined)',
);
}
const line = `${serialized}\n`;
const byteLength = Buffer.byteLength(line, 'utf8');
if (maxBytes !== null && byteLength > maxBytes) {
throw cliEmitError(
CLI_EMIT_ERROR_CODES.PACKET_TOO_LARGE,
`Serialized packet is ${byteLength} bytes, exceeding maxBytes limit of ${maxBytes}`,
{ byteLength, maxBytes },
);
}
return new Promise((resolve, reject) => {
const onWriteSettled = (err) => {
if (err) {
reject(cliEmitError(
CLI_EMIT_ERROR_CODES.WRITE_FAILED,
`emitJsonPacket failed to write packet to stream: ${err.message}`,
{ cause: err.message },
));
return;
}
process.exitCode = exitCode;
const result = { ok: true, bytes_written: byteLength, exit_code: exitCode };
resolve(result);
if (typeof exit === 'function') {
exit(exitCode);
}
};
try {
stream.write(line, 'utf8', onWriteSettled);
} catch (err) {
reject(cliEmitError(
CLI_EMIT_ERROR_CODES.WRITE_FAILED,
`emitJsonPacket failed to write packet to stream: ${err.message}`,
{ cause: err.message },
));
}
});
}
import { existsSync, readFileSync, readdirSync } from 'node:fs';
import { join, relative } from 'node:path';
export const DEPENDENCY_PIN_ERROR_CODES = Object.freeze({
INVALID_ARGS: 'invalid_args',
LOCKFILE_MISSING: 'lockfile_missing',
LOCKFILE_UNPARSEABLE: 'lockfile_unparseable',
PACKAGE_NOT_IN_LOCKFILE: 'package_not_in_lockfile',
PACKAGE_NOT_INSTALLED: 'package_not_installed',
INSTALLED_VERSION_MISMATCH: 'installed_version_mismatch',
MULTIPLE_INSTALLED_VERSIONS: 'multiple_installed_versions',
});
function pinError(code, message, details = null) {
const error = new Error(message);
error.code = code;
if (details && typeof details === 'object') {
error.details = details;
}
return error;
}
function assertNonEmptyString(value, label) {
if (typeof value !== 'string' || value.trim().length === 0) {
throw pinError(
DEPENDENCY_PIN_ERROR_CODES.INVALID_ARGS,
`${label} must be a non-empty string`,
);
}
}
function readJsonFile(filePath, errorCode, label) {
if (!existsSync(filePath)) {
throw pinError(errorCode, `${label} not found: ${filePath}`);
}
try {
return JSON.parse(readFileSync(filePath, 'utf8'));
} catch (cause) {
const error = pinError(errorCode, `${label} is not valid JSON: ${filePath}`);
error.cause = cause;
throw error;
}
}
function lockfileKeyForPackage(packageName) {
return `node_modules/${packageName}`;
}
function readPackageVersion(packageDir) {
const packageJsonPath = join(packageDir, 'package.json');
const packageJson = readJsonFile(
packageJsonPath,
DEPENDENCY_PIN_ERROR_CODES.INSTALLED_VERSION_MISMATCH,
'package.json',
);
if (typeof packageJson.version !== 'string' || packageJson.version.length === 0) {
throw pinError(
DEPENDENCY_PIN_ERROR_CODES.INSTALLED_VERSION_MISMATCH,
`package.json at ${packageDir} is missing a version`,
);
}
return packageJson.version;
}
function collectInstalledInstances(packageRoot, packageName) {
const instances = [];
function walkModulesDir(modulesDir) {
if (!existsSync(modulesDir)) return;
for (const entry of readdirSync(modulesDir, { withFileTypes: true })) {
if (!entry.isDirectory() || entry.name === '.bin') continue;
if (entry.name.startsWith('@')) {
const scopeDir = join(modulesDir, entry.name);
for (const scopedEntry of readdirSync(scopeDir, { withFileTypes: true })) {
if (!scopedEntry.isDirectory()) continue;
const packageDir = join(scopeDir, scopedEntry.name);
const fullName = `${entry.name}/${scopedEntry.name}`;
if (fullName === packageName) {
instances.push({
packageName: fullName,
version: readPackageVersion(packageDir),
lockPath: relative(packageRoot, packageDir).split('\\').join('/'),
});
}
walkModulesDir(join(packageDir, 'node_modules'));
}
continue;
}
const packageDir = join(modulesDir, entry.name);
if (entry.name === packageName) {
instances.push({
packageName: entry.name,
version: readPackageVersion(packageDir),
lockPath: relative(packageRoot, packageDir).split('\\').join('/'),
});
}
walkModulesDir(join(packageDir, 'node_modules'));
}
}
walkModulesDir(join(packageRoot, 'node_modules'));
return instances;
}
function getLockfilePackageVersion(lockfile, packageName) {
const lockKey = lockfileKeyForPackage(packageName);
const entry = lockfile.packages?.[lockKey];
if (!entry || typeof entry.version !== 'string' || entry.version.length === 0) {
return null;
}
return entry.version;
}
function assertPackagePins({
packageRoot,
packageName,
lockfile,
}) {
const expectedVersion = getLockfilePackageVersion(lockfile, packageName);
if (!expectedVersion) {
throw pinError(
DEPENDENCY_PIN_ERROR_CODES.PACKAGE_NOT_IN_LOCKFILE,
`Security package "${packageName}" is missing from package-lock.json`,
{ package: packageName, lockPath: lockfileKeyForPackage(packageName) },
);
}
const instances = collectInstalledInstances(packageRoot, packageName);
if (instances.length === 0) {
throw pinError(
DEPENDENCY_PIN_ERROR_CODES.PACKAGE_NOT_INSTALLED,
`Security package "${packageName}" is not installed under node_modules`,
{ package: packageName, expectedVersion },
);
}
const installedVersions = [...new Set(instances.map((instance) => instance.version))];
if (installedVersions.length > 1) {
throw pinError(
DEPENDENCY_PIN_ERROR_CODES.MULTIPLE_INSTALLED_VERSIONS,
`Security package "${packageName}" has multiple installed versions: ${installedVersions.join(', ')}`,
{
package: packageName,
expectedVersion,
installedVersions,
instances,
},
);
}
const [installedVersion] = installedVersions;
if (installedVersion !== expectedVersion) {
throw pinError(
DEPENDENCY_PIN_ERROR_CODES.INSTALLED_VERSION_MISMATCH,
`Security package "${packageName}" is installed as ${installedVersion}, but package-lock.json pins ${expectedVersion}`,
{
package: packageName,
expectedVersion,
installedVersion,
instances,
},
);
}
for (const instance of instances) {
const lockEntry = lockfile.packages?.[instance.lockPath];
if (!lockEntry?.version) continue;
if (lockEntry.version !== instance.version) {
throw pinError(
DEPENDENCY_PIN_ERROR_CODES.INSTALLED_VERSION_MISMATCH,
`Security package "${packageName}" at ${instance.lockPath} is installed as ${instance.version}, but package-lock.json pins ${lockEntry.version}`,
{
package: packageName,
lockPath: instance.lockPath,
expectedVersion: lockEntry.version,
installedVersion: instance.version,
},
);
}
}
return {
package: packageName,
expectedVersion,
installedVersion,
instances,
};
}
export function assertInstalledSecurityPins({
packageRoot,
packages,
} = {}) {
assertNonEmptyString(packageRoot, 'packageRoot');
if (!Array.isArray(packages) || packages.length === 0) {
throw pinError(
DEPENDENCY_PIN_ERROR_CODES.INVALID_ARGS,
'packages must be a non-empty array of package names',
);
}
const normalizedPackages = packages.map((packageName, index) => {
if (typeof packageName !== 'string' || packageName.trim().length === 0) {
throw pinError(
DEPENDENCY_PIN_ERROR_CODES.INVALID_ARGS,
`packages[${index}] must be a non-empty string`,
);
}
return packageName.trim();
});
const lockfile = readJsonFile(
join(packageRoot, 'package-lock.json'),
DEPENDENCY_PIN_ERROR_CODES.LOCKFILE_MISSING,
'package-lock.json',
);
if (!lockfile || typeof lockfile !== 'object' || !lockfile.packages) {
throw pinError(
DEPENDENCY_PIN_ERROR_CODES.LOCKFILE_UNPARSEABLE,
'package-lock.json must contain a packages object',
);
}
const results = normalizedPackages.map((packageName) => assertPackagePins({
packageRoot,
packageName,
lockfile,
}));
return {
ok: true,
packageRoot,
packages: results,
};
}
+5
-3
{
"name": "attepack",
"version": "0.1.1",
"version": "0.2.1",
"description": "Shared library for packet envelopes, schema contracts, and error packet normalization across the Forest tool ecosystem",

@@ -19,3 +19,5 @@ "type": "module",

"./diagnostics": "./src/diagnostics.js",
"./evidence-protocol": "./src/evidence-protocol.js"
"./cli-emit": "./src/cli-emit.js",
"./evidence-protocol": "./src/evidence-protocol.js",
"./dependency-pins": "./src/dependency-pins.js"
},

@@ -26,3 +28,3 @@ "scripts": {

"dependencies": {
"atteguard": "0.2.0"
"atteguard": "0.2.1"
},

@@ -29,0 +31,0 @@ "keywords": [

+184
-2

@@ -15,3 +15,9 @@ # Attepack

| `attepack/envelope` | Enforces packet envelope validation (`type`, `schema_version`, `observed_at`, `ok`) |
| `attepack/errors` | Known error codes plus `buildFormattedError` for stable `{ code, message, details? }` shapes |
| `attepack/sanitize` | Redacts sensitive text from error messages and allowlists error detail fields |
| `attepack/receipts` | Builds and validates mutation receipts bound to SHA-256 digests |
| `attepack/diagnostics` | Builds diagnostic events and validates fail-closed sink command argv |
| `attepack/cli-emit` | Fail-closed CLI JSON emit helper: flush-before-exit and a typed too-large error |
| `attepack/evidence-protocol` | Validates neutral producer-native evidence packet conformance without product-specific schemas |
| `attepack/dependency-pins` | Asserts installed security packages match `package-lock.json` across nested `node_modules` |

@@ -37,2 +43,57 @@ ## Install

Error formatting:
```js
import { ATTEPACK_ERROR_CODES, buildFormattedError } from 'attepack/errors';
const error = buildFormattedError(
ATTEPACK_ERROR_CODES.INVALID_ARGS,
'Missing required field',
{ field: 'schema_version' }
);
// { code: 'invalid_args', message: 'Missing required field', details: { field: 'schema_version' } }
```
Message and details sanitization:
```js
import { sanitizeErrorMessage, sanitizeErrorDetails } from 'attepack/sanitize';
const message = sanitizeErrorMessage('failed under a local home path');
const details = sanitizeErrorDetails({
field: 'credential_field',
reason: 'rejected',
value: 'redacted-before-call'
});
```
Mutation receipts:
```js
import { buildMutationReceipt } from 'attepack/receipts';
const digest = 'a'.repeat(64);
const receipt = buildMutationReceipt({
mutationKind: 'cr_merged',
identityDigest: digest,
intentDigest: digest,
providerId: 'gitea-api',
remoteName: 'origin',
repoId: 'example/tool'
});
```
Diagnostic events:
```js
import { buildDiagnosticEvent } from 'attepack/diagnostics';
const event = buildDiagnosticEvent({
tool: 'example-tool',
command: 'doctor',
failureClass: 'config_invalid',
details: { field: 'baseUrl' }
});
```
## Neutral Evidence Protocol

@@ -56,3 +117,124 @@

Attepack rejects legacy producer-section fields by default. To reject coupling
to a downstream product namespace, pass `forbiddenProductNamespaces`.
Attepack rejects legacy producer-section fields and the `atteway` product
namespace by default. Override with `forbiddenProductNamespaces` when a caller
needs a different forbid-list.
The same module also owns neutral, product-independent evidence binding
mechanics for guidance-lifecycle packets. Producers can use these primitives to
bind packets to public repository identity, exact candidate base/head SHAs,
merged source commits, capability-contract digests, source-artifact digests,
parent evidence digests, multi-target receipt summaries, safe relative
evidence references, and explicit authority limits without creating
Skillpress-, Verigram-, Waylane-, or Atteway-specific envelope forks.
```js
import {
calculateEvidenceDigest,
validateNeutralEvidencePacket,
} from 'attepack/evidence-protocol';
const impact = {
type: 'skillpress.guidance_impact.v1',
schema_version: 1,
ok: true,
producer: { id: 'skillpress' },
repository: { id: 'attebury/skillpress' },
candidate: {
base_sha: '1111111111111111111111111111111111111111',
head_sha: '2222222222222222222222222222222222222222',
},
capability_contracts: [{
capability_id: 'skillpress.capability_contracts',
digest: 'sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa',
}],
source_artifacts: [{
artifact_id: 'skillpress-repo-skill',
digest: 'sha256:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb',
}],
authority: {
role: 'advisory',
owns: ['guidance_impact'],
not_authority_for: ['merge', 'release', 'closeout', 'software_correctness'],
},
limitations: [{ code: 'semantic_review_not_included' }],
evidence_refs: ['evidence/skillpress-impact.json'],
};
impact.evidence_digest = calculateEvidenceDigest(impact);
validateNeutralEvidencePacket(impact, { bindingProfile: 'candidate' });
```
Use `bindingProfile: 'candidate'` for pre-merge candidate evidence,
`bindingProfile: 'semantic_review'` for candidate-bound review receipts that
chain to parent evidence, and `bindingProfile: 'merged_source'` for
merged-commit receipts with configured/attempted/verified target-set summaries.
The digest is deterministic canonical JSON with sorted object keys and bounded
arrays, strings, objects, and depth.
## Security dependency pins
Install with `npm ci`, then assert the installed tree matches the lockfile for
security-sensitive packages such as `atteguard` and `attepack`:
```js
import { assertInstalledSecurityPins } from 'attepack/dependency-pins';
assertInstalledSecurityPins({
packageRoot: process.cwd(),
packages: ['atteguard', 'attepack'],
});
```
Use this in CI gates and `doctor` checks so a stale or nested duplicate install
cannot pass while the lockfile documents a different version.
## Fail-closed CLI JSON emit
`console.log(JSON.stringify(packet)); process.exit(0)` can race the async
flush of `process.stdout` when stdout is a pipe, letting a CLI exit `0` while
emitting truncated, unparseable JSON. `emitJsonPacket` closes that race: it
only resolves (and only ever calls an injected exit hook) after the
stream's write callback confirms the data was flushed.
```js
import { emitJsonPacket } from 'attepack/cli-emit';
await emitJsonPacket(
{ type: 'wayline.verify.v1', ok: true, validity: 'authoritative' },
{ exitCode: 0 },
);
// process.exitCode is now set to 0; Node exits naturally once the packet
// is confirmed flushed, so exit status is only ever reported after the
// write completes.
```
Pass `maxBytes` to fail closed on oversized packets instead of emitting a
partial payload; this throws synchronously with a typed error before any
write is attempted:
```js
import { emitJsonPacket, CLI_EMIT_ERROR_CODES } from 'attepack/cli-emit';
try {
await emitJsonPacket(packet, { maxBytes: 1_000_000 });
} catch (error) {
if (error.code === CLI_EMIT_ERROR_CODES.PACKET_TOO_LARGE) {
// error.details.byteLength / error.details.maxBytes
}
}
```
By default `emitJsonPacket` never forces `process.exit`; it sets
`process.exitCode` after the flush is confirmed and lets Node exit
naturally, which is sufficient to guarantee ordering. Callers that need a
hard exit (e.g. to sidestep lingering handles) can opt in with an `exit`
hook, which is still only invoked after the flush callback fires:
```js
await emitJsonPacket(packet, { exitCode: 1, exit: process.exit });
```
Note that exit status alone is not proof that stdout was not truncated by
something downstream of this helper (e.g. a consumer closing the pipe
early); `emitJsonPacket` only guarantees ordering between the write and the
exit signal it controls.

@@ -0,1 +1,2 @@

import crypto from 'node:crypto';
import { validatePacketEnvelope } from './envelope.js';

@@ -5,2 +6,3 @@

export const NEUTRAL_EVIDENCE_CONFORMANCE_PACKET_TYPE = 'neutral.evidence_packet_conformance.v1';
export const NEUTRAL_EVIDENCE_DIGEST_PREFIX = 'sha256:';
export const NEUTRAL_EVIDENCE_AUTHORITY_ROLES = Object.freeze([

@@ -13,3 +15,8 @@ 'authoritative',

]);
export const DEFAULT_FORBIDDEN_PRODUCT_NAMESPACES = Object.freeze([]);
export const NEUTRAL_EVIDENCE_BINDING_PROFILES = Object.freeze([
'candidate',
'merged_source',
'semantic_review',
]);
export const DEFAULT_FORBIDDEN_PRODUCT_NAMESPACES = Object.freeze(['atteway']);
export const LEGACY_PRODUCER_SECTION_FIELDS = Object.freeze([

@@ -21,5 +28,27 @@ 'section_id',

'producer_sections',
'atteway_section',
]);
const PRODUCT_COUPLING_FIELD_PATTERN = /(?:^|_)(?:type|schema|section|corpus|golden|contract|fixture|packet)(?:_|$)/i;
const SHA256_DIGEST = /^sha256:[0-9a-f]{64}$/;
const GIT_SHA = /^[0-9a-f]{40}(?:[0-9a-f]{24})?$/i;
const PUBLIC_REPO_ID = /^[a-z0-9][a-z0-9._-]{0,63}\/[a-z0-9][a-z0-9._-]{0,127}$/i;
const SAFE_ID = /^[a-z][a-z0-9._:-]{0,127}$/;
const SAFE_REASON = /^[a-z][a-z0-9_:-]{0,127}$/;
const SAFE_REF = /^[a-z0-9][a-z0-9._/-]{0,255}$/i;
const MAX_CANONICAL_DEPTH = 32;
const MAX_CANONICAL_ARRAY_ITEMS = 128;
const MAX_CANONICAL_OBJECT_KEYS = 128;
const MAX_CANONICAL_STRING_BYTES = 4096;
const MAX_CANONICAL_BYTES = 262144;
const MAX_DIGEST_REF_ITEMS = 64;
const MAX_TARGET_SET_ITEMS = 64;
const AUTHORITY_FORBIDDEN_CLAIMS = Object.freeze([
'merge_authority',
'closeout_authority',
'release_authority',
'software_correctness',
'work_judgment',
'forge_truth',
]);

@@ -42,2 +71,6 @@ function isObject(value) {

function add(issues, code, path, message) {
issues.push(issue(code, path, message));
}
function namespacePattern(namespace) {

@@ -86,2 +119,412 @@ const escaped = String(namespace).replace(/[.*+?^${}()|[\]\\]/g, '\\$&');

function canonicalizationError(code, message, path = '$') {
const error = new TypeError(message);
error.code = code;
error.path = path;
return error;
}
function canonicalize(value, path = '$', depth = 0, seen = new Set()) {
if (depth > MAX_CANONICAL_DEPTH) {
throw canonicalizationError('neutral_canonical_depth_exceeded', 'Neutral evidence value exceeds maximum canonicalization depth.', path);
}
if (value === null) return 'null';
if (typeof value === 'boolean') return value ? 'true' : 'false';
if (typeof value === 'number') {
if (!Number.isFinite(value)) {
throw canonicalizationError('neutral_canonical_nonfinite_number', 'Neutral evidence numbers must be finite.', path);
}
return JSON.stringify(value);
}
if (typeof value === 'string') {
if (Buffer.byteLength(value, 'utf8') > MAX_CANONICAL_STRING_BYTES) {
throw canonicalizationError('neutral_canonical_string_oversized', 'Neutral evidence string exceeds maximum canonicalization size.', path);
}
return JSON.stringify(value);
}
if (Array.isArray(value)) {
if (value.length > MAX_CANONICAL_ARRAY_ITEMS) {
throw canonicalizationError('neutral_canonical_array_oversized', 'Neutral evidence array exceeds maximum canonicalization size.', path);
}
return `[${value.map((item, index) => canonicalize(item, `${path}[${index}]`, depth + 1, seen)).join(',')}]`;
}
if (isObject(value)) {
if (seen.has(value)) {
throw canonicalizationError('neutral_canonical_circular_reference', 'Neutral evidence value contains a circular reference.', path);
}
seen.add(value);
try {
const keys = Object.keys(value).sort();
if (keys.length > MAX_CANONICAL_OBJECT_KEYS) {
throw canonicalizationError('neutral_canonical_object_oversized', 'Neutral evidence object exceeds maximum canonicalization size.', path);
}
const parts = keys.map((key) => {
if (value[key] === undefined) {
throw canonicalizationError('neutral_canonical_undefined', 'Neutral evidence objects must not contain undefined values.', `${path}.${key}`);
}
return `${JSON.stringify(key)}:${canonicalize(value[key], `${path}.${key}`, depth + 1, seen)}`;
});
return `{${parts.join(',')}}`;
} finally {
seen.delete(value);
}
}
throw canonicalizationError('neutral_canonical_unsupported_type', 'Neutral evidence value contains an unsupported type.', path);
}
function rootDigestInput(packet) {
if (!isObject(packet)) return packet;
const copy = {};
for (const key of Object.keys(packet)) {
if (key === 'evidence_digest') continue;
copy[key] = packet[key];
}
return copy;
}
export function canonicalizeNeutralJson(value) {
const canonical = canonicalize(value);
if (Buffer.byteLength(canonical, 'utf8') > MAX_CANONICAL_BYTES) {
throw canonicalizationError('neutral_canonical_packet_oversized', 'Neutral evidence canonical JSON exceeds maximum byte size.', '$');
}
return canonical;
}
export function calculateEvidenceDigest(value, options = {}) {
const input = options.omitRootEvidenceDigest === false ? value : rootDigestInput(value);
const canonical = canonicalizeNeutralJson(input);
return `${NEUTRAL_EVIDENCE_DIGEST_PREFIX}${crypto.createHash('sha256').update(canonical).digest('hex')}`;
}
export function isSha256Digest(value) {
return typeof value === 'string' && SHA256_DIGEST.test(value);
}
export function isPublicRepositoryId(value) {
return typeof value === 'string' && PUBLIC_REPO_ID.test(value);
}
export function isGitCommitSha(value) {
return typeof value === 'string' && GIT_SHA.test(value);
}
export function isSafeEvidenceRef(value) {
if (typeof value !== 'string' || !SAFE_REF.test(value)) return false;
if (value.startsWith('/') || value.includes('..') || value.includes('//') || value.includes('~')) return false;
if (/^[a-z][a-z0-9+.-]*:/i.test(value)) return false;
return true;
}
function validateRepository(packet, issues, options) {
const repoId = packet?.repository?.id ?? packet?.repo_id ?? null;
if (options.requireRepository && repoId == null) {
add(issues, 'repository_id_missing', '$.repository.id', 'Neutral evidence must declare a stable public repository identity.');
return;
}
if (repoId != null && !isPublicRepositoryId(repoId)) {
add(issues, 'repository_id_invalid', '$.repository.id', 'Repository identity must be a bounded public owner/repo id.');
}
if (options.expectedRepositoryId && repoId !== options.expectedRepositoryId) {
add(issues, 'repository_id_mismatch', '$.repository.id', 'Repository identity does not match expected target.');
}
}
function validateCandidate(packet, issues, options) {
const candidate = packet?.candidate;
if (options.requireCandidate && !isObject(candidate)) {
add(issues, 'candidate_binding_missing', '$.candidate', 'Candidate evidence must bind exact base and head SHAs.');
return;
}
if (!isObject(candidate)) return;
if (!isGitCommitSha(candidate.base_sha)) {
add(issues, 'candidate_base_sha_invalid', '$.candidate.base_sha', 'Candidate base_sha must be a full Git SHA.');
}
if (!isGitCommitSha(candidate.head_sha)) {
add(issues, 'candidate_head_sha_invalid', '$.candidate.head_sha', 'Candidate head_sha must be a full Git SHA.');
}
if (candidate.base_sha === candidate.head_sha) {
add(issues, 'candidate_sha_ambiguous', '$.candidate', 'Candidate base_sha and head_sha must be distinct.');
}
if (options.expectedBaseSha && candidate.base_sha !== options.expectedBaseSha) {
add(issues, 'candidate_base_sha_mismatch', '$.candidate.base_sha', 'Candidate base_sha does not match expected target.');
}
if (options.expectedHeadSha && candidate.head_sha !== options.expectedHeadSha) {
add(issues, 'candidate_head_sha_mismatch', '$.candidate.head_sha', 'Candidate head_sha does not match expected target.');
}
}
function validateMergedSource(packet, issues, options) {
const merged = packet?.merged_source;
if (options.requireMergedSource && !isObject(merged)) {
add(issues, 'merged_source_binding_missing', '$.merged_source', 'Merged-source evidence must bind an exact source commit.');
return;
}
if (!isObject(merged)) return;
if (!isGitCommitSha(merged.commit_sha)) {
add(issues, 'merged_source_commit_sha_invalid', '$.merged_source.commit_sha', 'Merged-source commit_sha must be a full Git SHA.');
}
if (options.expectedCommitSha && merged.commit_sha !== options.expectedCommitSha) {
add(issues, 'merged_source_commit_sha_mismatch', '$.merged_source.commit_sha', 'Merged-source commit_sha does not match expected target.');
}
}
function validateDigestRefs(values, field, idField, digestField, issues) {
if (values == null) return;
if (!Array.isArray(values)) {
add(issues, `${field}_invalid`, `$.${field}`, `${field} must be an array.`);
return;
}
if (values.length > MAX_DIGEST_REF_ITEMS) {
add(issues, `${field}_oversized`, `$.${field}`, `${field} exceeds the maximum item count.`);
return;
}
const seen = new Set();
values.forEach((item, index) => {
const path = `$.${field}[${index}]`;
if (!isObject(item)) {
add(issues, `${field}_item_invalid`, path, `${field} entries must be objects.`);
return;
}
const id = item[idField];
const digest = item[digestField];
if (typeof id !== 'string' || !SAFE_ID.test(id)) {
add(issues, `${field}_id_invalid`, `${path}.${idField}`, `${field} entry id must be a bounded safe identifier.`);
}
if (!isSha256Digest(digest)) {
add(issues, `${field}_digest_invalid`, `${path}.${digestField}`, `${field} entry digest must be sha256:<64 hex>`);
}
const key = `${id}:${digest}`;
if (seen.has(key)) {
add(issues, `${field}_duplicate`, path, `${field} entries must be unambiguous.`);
}
seen.add(key);
});
}
function validateParentEvidence(packet, issues, options) {
const parentEvidence = packet?.parent_evidence ?? null;
const inputEvidence = packet?.input_evidence ?? null;
if (options.requireParentEvidence && parentEvidence == null && inputEvidence == null) {
add(issues, 'parent_evidence_missing', '$.parent_evidence', 'Evidence chaining requires parent_evidence digest references.');
return;
}
validateDigestRefs(parentEvidence, 'parent_evidence', 'type', 'digest', issues);
validateDigestRefs(inputEvidence, 'input_evidence', 'type', 'digest', issues);
if (options.expectedParentDigest) {
const values = [
...(Array.isArray(parentEvidence) ? parentEvidence : []),
...(Array.isArray(inputEvidence) ? inputEvidence : []),
];
const found = values.some((item) => item?.digest === options.expectedParentDigest);
if (!found) {
add(issues, 'parent_evidence_digest_mismatch', '$.parent_evidence', 'Expected parent evidence digest was not present.');
}
}
}
function validateTargetSetArray(values, field, issues) {
if (!Array.isArray(values)) {
add(issues, 'target_set_invalid', `$.target_sets.${field}`, 'Target-set summaries must be arrays.');
return;
}
if (values.length > MAX_TARGET_SET_ITEMS) {
add(issues, 'target_set_oversized', `$.target_sets.${field}`, 'Target-set summary exceeds the maximum item count.');
return;
}
const seen = new Set();
values.forEach((item, index) => {
const path = `$.target_sets.${field}[${index}]`;
const id = typeof item === 'string' ? item : item?.id;
if (typeof id !== 'string' || !SAFE_ID.test(id)) {
add(issues, 'target_set_id_invalid', path, 'Target-set ids must be bounded safe identifiers.');
return;
}
if (seen.has(id)) {
add(issues, 'target_set_duplicate', path, 'Target-set ids must be unique within each set.');
}
seen.add(id);
if (isObject(item) && item.digest !== undefined && !isSha256Digest(item.digest)) {
add(issues, 'target_set_digest_invalid', `${path}.digest`, 'Target-set digests must be sha256:<64 hex> when present.');
}
});
}
function validateTargetSets(packet, issues, options) {
const targetSets = packet?.target_sets;
if (options.requireTargetSets && !isObject(targetSets)) {
add(issues, 'target_sets_missing', '$.target_sets', 'Multi-target receipts must summarize configured, attempted, and verified target sets.');
return;
}
if (!isObject(targetSets)) return;
for (const field of ['configured', 'attempted', 'verified']) {
validateTargetSetArray(targetSets[field], field, issues);
}
if (Array.isArray(targetSets.verified) && Array.isArray(targetSets.configured)) {
const configured = new Set(targetSets.configured.map((item) => (typeof item === 'string' ? item : item?.id)));
for (const item of targetSets.verified) {
const id = typeof item === 'string' ? item : item?.id;
if (id && !configured.has(id)) {
add(issues, 'target_set_verified_not_configured', '$.target_sets.verified', 'Verified targets must be part of the configured target set.');
}
}
}
}
function validateEvidenceRefs(packet, issues) {
if (packet?.evidence_refs == null) return;
if (!Array.isArray(packet.evidence_refs)) {
add(issues, 'evidence_refs_invalid', '$.evidence_refs', 'Evidence refs must be an array.');
return;
}
if (packet.evidence_refs.length > MAX_DIGEST_REF_ITEMS) {
add(issues, 'evidence_refs_oversized', '$.evidence_refs', 'Evidence refs exceed maximum item count.');
return;
}
packet.evidence_refs.forEach((ref, index) => {
if (!isSafeEvidenceRef(ref)) {
add(issues, 'evidence_ref_unsafe', `$.evidence_refs[${index}]`, 'Evidence refs must be bounded safe relative references.');
}
});
}
function validateAuthorityDeclarations(packet, issues, options) {
const authority = packet?.authority;
if (options.requireAuthority && !isObject(authority)) {
add(issues, 'authority_declaration_missing', '$.authority', 'Neutral evidence must declare authority and limits.');
return;
}
if (!isObject(authority)) return;
const notAuthorityFor = authority.not_authority_for ?? packet.not_authority_for ?? null;
if (!Array.isArray(notAuthorityFor) || notAuthorityFor.length === 0) {
add(issues, 'authority_limitations_missing', '$.authority.not_authority_for', 'Evidence authority declarations must include not_authority_for limits.');
} else {
notAuthorityFor.forEach((value, index) => {
if (typeof value !== 'string' || !SAFE_REASON.test(value)) {
add(issues, 'authority_limitation_invalid', `$.authority.not_authority_for[${index}]`, 'Authority limitations must be bounded safe tokens.');
}
});
}
const claims = [
...(Array.isArray(authority.owns) ? authority.owns : []),
...(Array.isArray(authority.grants) ? authority.grants : []),
];
claims.forEach((claim, index) => {
if (typeof claim !== 'string' || !SAFE_REASON.test(claim)) {
add(issues, 'authority_claim_invalid', `$.authority.owns[${index}]`, 'Authority claims must be bounded safe tokens.');
return;
}
if (AUTHORITY_FORBIDDEN_CLAIMS.includes(claim)) {
add(issues, 'authority_claim_escalation', `$.authority.owns[${index}]`, `Authority claim "${claim}" exceeds neutral evidence ownership.`);
}
});
}
function validateLimitations(packet, issues) {
if (packet?.limitations == null) return;
if (!Array.isArray(packet.limitations)) {
add(issues, 'limitations_invalid', '$.limitations', 'Limitations must be an array.');
return;
}
if (packet.limitations.length > MAX_DIGEST_REF_ITEMS) {
add(issues, 'limitations_oversized', '$.limitations', 'Limitations exceed maximum item count.');
return;
}
packet.limitations.forEach((limitation, index) => {
const path = `$.limitations[${index}]`;
const code = typeof limitation === 'string' ? limitation : limitation?.code;
if (typeof code !== 'string' || !SAFE_REASON.test(code)) {
add(issues, 'limitation_code_invalid', path, 'Limitation codes must be bounded safe tokens.');
}
});
}
function validateEvidenceDigest(packet, issues, options) {
if (options.requireEvidenceDigest && packet?.evidence_digest == null) {
add(issues, 'evidence_digest_missing', '$.evidence_digest', 'Evidence packets must include a deterministic evidence_digest when required.');
return;
}
if (packet?.evidence_digest == null) return;
if (!isSha256Digest(packet.evidence_digest)) {
add(issues, 'evidence_digest_invalid', '$.evidence_digest', 'evidence_digest must be sha256:<64 hex>.');
return;
}
let calculated;
try {
calculated = calculateEvidenceDigest(packet);
} catch (error) {
add(issues, error.code ?? 'evidence_digest_calculation_failed', error.path ?? '$', error.message);
return;
}
if (packet.evidence_digest !== calculated) {
add(issues, 'evidence_digest_mismatch', '$.evidence_digest', 'evidence_digest does not match deterministic canonical packet digest.');
}
}
function requirementsForProfile(profile) {
if (profile == null) return {};
if (!NEUTRAL_EVIDENCE_BINDING_PROFILES.includes(profile)) {
return { invalidProfile: true };
}
if (profile === 'candidate') {
return {
requireRepository: true,
requireCandidate: true,
requireAuthority: true,
requireEvidenceDigest: true,
};
}
if (profile === 'merged_source') {
return {
requireRepository: true,
requireMergedSource: true,
requireAuthority: true,
requireEvidenceDigest: true,
requireTargetSets: true,
};
}
return {
requireRepository: true,
requireCandidate: true,
requireAuthority: true,
requireEvidenceDigest: true,
requireParentEvidence: true,
};
}
function collectBindingIssues(packet, options) {
const issues = [];
const profileRequirements = requirementsForProfile(options.bindingProfile);
if (profileRequirements.invalidProfile) {
add(issues, 'binding_profile_invalid', '$', 'Neutral evidence binding profile is not supported.');
return issues;
}
const checks = {
...profileRequirements,
requireRepository: options.requireRepository === true || profileRequirements.requireRepository === true,
requireCandidate: options.requireCandidate === true || profileRequirements.requireCandidate === true,
requireMergedSource: options.requireMergedSource === true || profileRequirements.requireMergedSource === true,
requireParentEvidence: options.requireParentEvidence === true || profileRequirements.requireParentEvidence === true,
requireTargetSets: options.requireTargetSets === true || profileRequirements.requireTargetSets === true,
requireAuthority: options.requireAuthority === true || profileRequirements.requireAuthority === true,
requireEvidenceDigest: options.requireEvidenceDigest === true || profileRequirements.requireEvidenceDigest === true,
expectedRepositoryId: options.expectedRepositoryId,
expectedBaseSha: options.expectedBaseSha,
expectedHeadSha: options.expectedHeadSha,
expectedCommitSha: options.expectedCommitSha,
expectedParentDigest: options.expectedParentDigest,
};
validateRepository(packet, issues, checks);
validateCandidate(packet, issues, checks);
validateMergedSource(packet, issues, checks);
validateDigestRefs(packet?.capability_contracts, 'capability_contracts', 'capability_id', 'digest', issues);
validateDigestRefs(packet?.source_artifacts, 'source_artifacts', 'artifact_id', 'digest', issues);
validateParentEvidence(packet, issues, checks);
validateTargetSets(packet, issues, checks);
validateEvidenceRefs(packet, issues);
validateAuthorityDeclarations(packet, issues, checks);
validateLimitations(packet, issues);
validateEvidenceDigest(packet, issues, checks);
return issues;
}
function collectEnvelopeIssues(packet, options) {

@@ -120,2 +563,15 @@ const issues = [];

forbiddenProductNamespaces: Array.from(forbiddenProductNamespaces),
bindingProfile: options.bindingProfile ?? null,
requireRepository: options.requireRepository === true,
requireCandidate: options.requireCandidate === true,
requireMergedSource: options.requireMergedSource === true,
requireParentEvidence: options.requireParentEvidence === true,
requireTargetSets: options.requireTargetSets === true,
requireAuthority: options.requireAuthority === true,
requireEvidenceDigest: options.requireEvidenceDigest === true,
expectedRepositoryId: options.expectedRepositoryId,
expectedBaseSha: options.expectedBaseSha,
expectedHeadSha: options.expectedHeadSha,
expectedCommitSha: options.expectedCommitSha,
expectedParentDigest: options.expectedParentDigest,
};

@@ -133,2 +589,3 @@ }

issues.push(...collectProductCouplingIssues(packet, normalized.forbiddenProductNamespaces));
issues.push(...collectBindingIssues(packet, normalized));
}

@@ -143,2 +600,3 @@

forbidden_product_namespaces: normalized.forbiddenProductNamespaces,
...(normalized.bindingProfile ? { binding_profile: normalized.bindingProfile } : {}),
},

@@ -145,0 +603,0 @@ producer: normalized.producerId ? { id: normalized.producerId } : null,

@@ -40,7 +40,25 @@ export {

export {
DEPENDENCY_PIN_ERROR_CODES,
assertInstalledSecurityPins,
} from './dependency-pins.js';
export {
CLI_EMIT_ERROR_CODES,
emitJsonPacket,
} from './cli-emit.js';
export {
NEUTRAL_EVIDENCE_PROTOCOL_SCHEMA_VERSION,
NEUTRAL_EVIDENCE_CONFORMANCE_PACKET_TYPE,
NEUTRAL_EVIDENCE_DIGEST_PREFIX,
NEUTRAL_EVIDENCE_AUTHORITY_ROLES,
NEUTRAL_EVIDENCE_BINDING_PROFILES,
DEFAULT_FORBIDDEN_PRODUCT_NAMESPACES,
LEGACY_PRODUCER_SECTION_FIELDS,
canonicalizeNeutralJson,
calculateEvidenceDigest,
isSha256Digest,
isPublicRepositoryId,
isGitCommitSha,
isSafeEvidenceRef,
evaluateNeutralEvidencePacket,

@@ -47,0 +65,0 @@ validateNeutralEvidencePacket,