agentera 3.0.0-dev.72 → 3.0.0-dev.75

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (93) hide show
  1. package/README.md +111 -0
  2. package/bundle/.agentera-build-source.json +8 -0
  3. package/bundle/CHANGELOG.md +17 -17
  4. package/bundle/extract-corpus-parity.json +1 -1
  5. package/bundle/references/adapters/package-publication.json +49 -53
  6. package/bundle/references/adapters/package-registry.yaml +5 -1
  7. package/bundle/references/adapters/package-surface-characterization.md +6 -4
  8. package/bundle/references/analysis/evidence-tier-authority.yaml +45 -4
  9. package/bundle/references/analysis/personal-glossary-evaluation-authority.yaml +175 -0
  10. package/bundle/references/analysis/personal-glossary-evaluation-corpus.yaml +291 -0
  11. package/bundle/references/analysis/personal-glossary-holdout.yaml +192 -0
  12. package/bundle/references/analysis/verification-policy.yaml +8 -13
  13. package/bundle/references/artifacts/glossary-entry-contract.yaml +1533 -35
  14. package/bundle/references/cli/update-channels.yaml +2 -2
  15. package/bundle/references/cli/vocabulary.md +1 -1
  16. package/bundle/references/meta/retained-reference-authority.yaml +16 -1
  17. package/bundle/skills/agentera/capabilities/profile/schemas/artifacts.yaml +11 -7
  18. package/bundle/skills/agentera/capabilities/profile/schemas/exit.yaml +6 -3
  19. package/bundle/skills/agentera/capabilities/profile/schemas/validation.yaml +13 -7
  20. package/bundle/skills/agentera/schemas/artifacts/glossary.yaml +5 -0
  21. package/bundle/skills/agentera/schemas/artifacts/plan.yaml +7 -0
  22. package/dist/.agentera-build-source.json +8 -0
  23. package/dist/analytics/extractCorpus/copilotSessions.js +13 -4
  24. package/dist/analytics/extractCorpus/core.js +223 -3
  25. package/dist/analytics/extractCorpus/corpus.js +18 -1
  26. package/dist/analytics/extractCorpus/cursorSessions.js +22 -4
  27. package/dist/analytics/extractCorpus/evidenceTiers.js +123 -17
  28. package/dist/analytics/extractCorpus/filesystemSources.js +4 -0
  29. package/dist/analytics/extractCorpus/index.js +2 -2
  30. package/dist/analytics/extractCorpus/jsonlSessions.js +52 -3
  31. package/dist/analytics/extractCorpus/sqliteSessions.js +12 -3
  32. package/dist/analytics/personalGlossaryAdmission.js +26 -46
  33. package/dist/analytics/personalGlossaryCandidateProjection.js +692 -0
  34. package/dist/analytics/personalGlossaryCandidateProjectionExcerpts.js +144 -0
  35. package/dist/analytics/personalGlossaryCandidateReadView.js +23 -0
  36. package/dist/analytics/personalGlossaryCurrentGeneration.js +25 -0
  37. package/dist/analytics/personalGlossaryDecision.js +203 -0
  38. package/dist/analytics/personalGlossaryExplicit.js +788 -0
  39. package/dist/analytics/personalGlossaryExplicitMining.js +285 -0
  40. package/dist/analytics/personalGlossaryExplicitScope.js +41 -0
  41. package/dist/analytics/personalGlossaryExplicitSegments.js +418 -0
  42. package/dist/analytics/personalGlossaryExplicitTypes.js +26 -0
  43. package/dist/analytics/personalGlossaryLexicalClassifier.js +175 -0
  44. package/dist/analytics/personalGlossaryProfile.js +13 -3
  45. package/dist/analytics/personalGlossaryRecurrence.js +618 -0
  46. package/dist/analytics/personalGlossaryReviewRecordStorage.js +428 -0
  47. package/dist/analytics/personalGlossaryReviewRecords.js +332 -0
  48. package/dist/capabilities/index.js +5 -1
  49. package/dist/capabilities/profile/instructions.js +50 -15
  50. package/dist/cli/commands/personalGlossaryCandidateReads.js +744 -0
  51. package/dist/cli/commands/personalGlossaryDecision.js +349 -0
  52. package/dist/cli/commands/personalGlossaryPublish.js +484 -0
  53. package/dist/cli/commands/personalGlossaryReviewRecordReads.js +209 -0
  54. package/dist/cli/commands/personalGlossaryReviewRecords.js +623 -0
  55. package/dist/cli/commands/prime/briefOrientation.js +118 -19
  56. package/dist/cli/commands/report.js +18 -2
  57. package/dist/cli/commands/schema.js +179 -0
  58. package/dist/cli/commands/verify.js +51 -9
  59. package/dist/cli/dispatch/lifecycle.js +51 -6
  60. package/dist/cli/help.js +48 -2
  61. package/dist/cli/migrationRequired.js +4 -1
  62. package/dist/core/atomicWriter.js +4 -1
  63. package/dist/core/developmentInvocation.js +4 -1
  64. package/dist/core/yaml.js +53 -1
  65. package/dist/eval/glossaryEvaluation.js +751 -0
  66. package/dist/eval/glossaryEvaluationProcess.js +29 -0
  67. package/dist/eval/glossaryEvaluationRunner.js +571 -0
  68. package/dist/eval/glossaryEvaluationSuccessReport.js +297 -0
  69. package/dist/registries/activationTuples.js +31 -3
  70. package/dist/registries/evidenceTierContract.js +4 -0
  71. package/dist/registries/explicitSegmentGrammarContract.js +619 -0
  72. package/dist/registries/glossaryCandidateContractPaths.js +6 -0
  73. package/dist/registries/glossaryCandidateContracts.js +820 -0
  74. package/dist/registries/glossaryCandidateDecisionAuthority.js +119 -0
  75. package/dist/registries/glossaryCandidateDecisionContract.js +62 -0
  76. package/dist/registries/glossaryCandidateProjectionAuthority.js +197 -0
  77. package/dist/registries/glossaryCandidateProjectionContract.js +104 -0
  78. package/dist/registries/glossaryEntryContract.js +105 -103
  79. package/dist/registries/glossaryEntryTemporal.js +12 -0
  80. package/dist/registries/glossaryMiningAuthority.js +764 -0
  81. package/dist/registries/glossaryProfileFullContract.js +24 -0
  82. package/dist/registries/glossaryReviewRecordsAuthority.js +426 -0
  83. package/dist/registries/glossaryReviewRecordsContract.js +129 -0
  84. package/dist/registries/glossaryTermIdentity.js +122 -0
  85. package/dist/registries/packagePublication.js +3 -3
  86. package/dist/registries/personalGlossaryContracts.js +1 -0
  87. package/dist/release/releaseMetadata.js +15 -8
  88. package/dist/upgrade/npxPlatformStatus.js +2 -1
  89. package/dist/upgrade/projectIntegration.js +7 -1
  90. package/dist/validate/activationArtifactEvidence.js +18 -4
  91. package/dist/validate/activationEvidenceManifest.js +101 -22
  92. package/package.json +2 -4
  93. package/dist/cli/commands/personalGlossary.js +0 -176
@@ -1,3 +1,4 @@
1
+ import { createHash } from "node:crypto";
1
2
  function escapeRegExp(value) {
2
3
  return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
3
4
  }
@@ -15,3 +16,124 @@ export function unicodeCaselessExact(left, right) {
15
16
  const literal = escapeRegExp(left);
16
17
  return new RegExp(`^(?:${literal})$(?![\\s\\S])`, "iu").test(right);
17
18
  }
19
+ function compareCodePoints(left, right) {
20
+ const leftCodePoint = left.codePointAt(0) ?? 0;
21
+ const rightCodePoint = right.codePointAt(0) ?? 0;
22
+ return leftCodePoint - rightCodePoint;
23
+ }
24
+ export function compareGlossaryUnicodeStrings(left, right) {
25
+ const leftScalars = [...left];
26
+ const rightScalars = [...right];
27
+ for (let index = 0; index < Math.min(leftScalars.length, rightScalars.length); index += 1) {
28
+ const comparison = compareCodePoints(leftScalars[index], rightScalars[index]);
29
+ if (comparison !== 0)
30
+ return comparison;
31
+ }
32
+ return leftScalars.length - rightScalars.length;
33
+ }
34
+ function singleScalar(value) {
35
+ return [...value].length === 1 ? value : null;
36
+ }
37
+ /**
38
+ * Build the same simple-case equivalence key used by the stable identity.
39
+ * Lowercase mapping is the primary Unicode simple-fold projection. A
40
+ * lowercased uppercase mapping is admitted only when the existing equality
41
+ * matcher confirms it, which avoids transitive casing mistakes such as
42
+ * treating ASCII `I` and dotless `ı` as equal.
43
+ */
44
+ function unicodeSimpleCaseFoldKey(value) {
45
+ return [...value]
46
+ .map((character) => {
47
+ const lower = singleScalar(character.toLowerCase()) ?? character;
48
+ const upper = singleScalar(character.toUpperCase());
49
+ const upperLower = upper === null ? null : singleScalar(upper.toLowerCase());
50
+ if (upperLower !== null && unicodeCaselessExact(character, upperLower)) {
51
+ return upperLower;
52
+ }
53
+ return lower;
54
+ })
55
+ .join("");
56
+ }
57
+ function sha256Utf8(value) {
58
+ return createHash("sha256").update(value, "utf8").digest("hex");
59
+ }
60
+ function requireNonEmpty(value, field) {
61
+ if (typeof value !== "string" || value.trim().length === 0) {
62
+ throw new TypeError(`${field} must be a non-empty string`);
63
+ }
64
+ return value;
65
+ }
66
+ /**
67
+ * Return a content-addressed identity for one Unicode caseless-exact term.
68
+ * The original spelling is not changed or stored in the identity key.
69
+ */
70
+ export function stableGlossaryTermIdentity(term) {
71
+ const value = requireNonEmpty(term, "term");
72
+ const key = unicodeSimpleCaseFoldKey(value);
73
+ return sha256Utf8(JSON.stringify({
74
+ schema_version: "agentera.personalGlossaryTermIdentity.v1",
75
+ key,
76
+ }));
77
+ }
78
+ export const GLOSSARY_CANDIDATE_SCOPES = ["personal", "project", "ambiguous"];
79
+ export function isGlossaryCandidateScope(value) {
80
+ return GLOSSARY_CANDIDATE_SCOPES.includes(value);
81
+ }
82
+ export function canonicalGlossaryJson(value) {
83
+ if (value === undefined)
84
+ throw new TypeError("canonical JSON cannot contain undefined");
85
+ if (value === null || typeof value !== "object")
86
+ return JSON.stringify(value);
87
+ if (Array.isArray(value))
88
+ return `[${value.map(canonicalGlossaryJson).join(",")}]`;
89
+ const record = value;
90
+ return `{${Object.keys(record)
91
+ .sort(compareGlossaryUnicodeStrings)
92
+ .map((key) => `${JSON.stringify(key)}:${canonicalGlossaryJson(record[key])}`)
93
+ .join(",")}}`;
94
+ }
95
+ export function glossaryCanonicalSha256(value) {
96
+ return sha256Utf8(canonicalGlossaryJson(value));
97
+ }
98
+ /**
99
+ * Return a content-addressed candidate revision without mining or publishing.
100
+ * Evidence order is not semantic, but every evidence identity remains bound.
101
+ */
102
+ export function glossaryCandidateRevision(input) {
103
+ const stableTermIdentity = requireNonEmpty(input.stable_term_identity, "stable_term_identity");
104
+ const meaning = requireNonEmpty(input.meaning, "meaning");
105
+ const policyVersion = requireNonEmpty(input.policy_version, "policy_version");
106
+ const generation = requireNonEmpty(input.generation, "generation");
107
+ if (!Array.isArray(input.evidence))
108
+ throw new TypeError("evidence must be a list");
109
+ if (!isGlossaryCandidateScope(input.scope)) {
110
+ throw new TypeError("scope must be personal, project, or ambiguous");
111
+ }
112
+ const evidence = input.evidence.map((item) => {
113
+ requireNonEmpty(item.source_id, "evidence.source_id");
114
+ requireNonEmpty(item.evidence_anchor, "evidence.evidence_anchor");
115
+ return item;
116
+ });
117
+ const canonicalEvidence = new Set();
118
+ for (const item of evidence) {
119
+ const canonical = canonicalGlossaryJson(item);
120
+ if (canonicalEvidence.has(canonical)) {
121
+ throw new TypeError("evidence must not contain duplicate canonical evidence records");
122
+ }
123
+ canonicalEvidence.add(canonical);
124
+ }
125
+ evidence.sort((left, right) => {
126
+ const leftCanonical = canonicalGlossaryJson(left);
127
+ const rightCanonical = canonicalGlossaryJson(right);
128
+ return compareGlossaryUnicodeStrings(leftCanonical, rightCanonical);
129
+ });
130
+ return sha256Utf8(canonicalGlossaryJson({
131
+ schema_version: "agentera.personalGlossaryCandidateRevision.v1",
132
+ stable_term_identity: stableTermIdentity,
133
+ meaning,
134
+ scope: input.scope,
135
+ evidence,
136
+ policy_version: policyVersion,
137
+ generation,
138
+ }));
139
+ }
@@ -52,12 +52,12 @@ export const ACTIVATION_CENSUS_AUTHORITY = deepFreeze({
52
52
  cli: { count: 27, sha256: "986a637f5c11c57ab7a897a01bd291d0811846e5339c3831c715932cf9659ad4" },
53
53
  capability: { count: 12, sha256: "007e1157a892fec54182b1803aeaa442cf8e2e332e6a055c3bd020e2b0731867" },
54
54
  runtime: { count: 81, sha256: "03fe600a3a27f05daf0d1b59c16b00bedc354a13c70ea60be0b457175fdf743c" },
55
- reference: { count: 22, sha256: "2b48c82bb7ee10388d9ebb4fa7ea3743ea82a7e9aaedfcd5a858eeedb9d891ae" },
55
+ reference: { count: 25, sha256: "2094946cd6e189c6ab3db16dd0725596cec759abcea88d8335b05215b65ccfc2" },
56
56
  state: { count: 38, sha256: "7ab0dd6ef1b1b1ce66bd9ea94d1de3d542528233e928a44102e289bf12416681" },
57
- package: { count: 66, sha256: "3548af7e84151c90690a6eb3d1cb6c7847f39b290161ecab599d8cb1bf0d2cb0" },
57
+ package: { count: 67, sha256: "d73cd86fed39057cf9ae73e462a59e842372392b1e88846f2b220378a2ff75cc" },
58
58
  bootstrap: { count: 34, sha256: "71c2038744e2518a6adb722acbb5f9352bddfb5d1c92eeb0e347297ef2ca2f1e" },
59
59
  },
60
- total: { count: 280, sha256: "051ed9a57857a39e11ed8fb583bff9d2a67a306c5ab897a0ee829212468bc23a" },
60
+ total: { count: 284, sha256: "3f30b26839aec47639f2197c9efda5caf2a73d5afca9fb38ff9a341e18a72b98" },
61
61
  });
62
62
  /** Each dimension names the production contract it observes independently. */
63
63
  export const ACTIVATION_EVIDENCE_SOURCES = deepFreeze({
@@ -0,0 +1 @@
1
+ export {};
@@ -184,7 +184,7 @@ function readPackageJson(root) {
184
184
  }
185
185
  return readJson(candidate);
186
186
  }
187
- function readUpdateChannels(root) {
187
+ export function readUpdateChannels(root) {
188
188
  const candidate = path.join(root, "references/cli/update-channels.yaml");
189
189
  if (!fs.existsSync(candidate)) {
190
190
  return null;
@@ -231,22 +231,22 @@ function pickChannelDefaults(authority) {
231
231
  stable: typeof defaults.stable === "string" ? defaults.stable : null,
232
232
  };
233
233
  }
234
- export function readReleaseMetadata(root = rootDefault()) {
234
+ export function readReleaseMetadata(root = rootDefault(), packageOverride) {
235
235
  const resolved = resolvePath(root);
236
236
  const registry = readRegistryVersion(resolved);
237
237
  const pkg = readPackageJson(resolved);
238
238
  const channels = readUpdateChannels(resolved);
239
239
  const bundle = readBundleSentinel(resolved);
240
- const topVersion = pkg && typeof pkg.version === "string" ? pkg.version : null;
240
+ const topVersion = packageOverride?.version ?? (pkg && typeof pkg.version === "string" ? pkg.version : null);
241
241
  const agenteraBlock = pkg && typeof pkg.agentera === "object" && pkg.agentera !== null
242
242
  ? pkg.agentera
243
243
  : null;
244
244
  const suiteVersion = agenteraBlock && typeof agenteraBlock.suiteVersion === "string"
245
245
  ? agenteraBlock.suiteVersion
246
246
  : null;
247
- const gitRef = agenteraBlock && typeof agenteraBlock.gitRef === "string"
247
+ const gitRef = packageOverride?.gitRef ?? (agenteraBlock && typeof agenteraBlock.gitRef === "string"
248
248
  ? agenteraBlock.gitRef
249
- : null;
249
+ : null);
250
250
  const { development, stable } = pickChannelDefaults(channels);
251
251
  return {
252
252
  authoritativeVersion: registry.version,
@@ -263,7 +263,7 @@ export function readReleaseMetadata(root = rootDefault()) {
263
263
  * Validate release-metadata coherence. Returns an empty array when the
264
264
  * source-tree version surfaces agree on a single release train.
265
265
  */
266
- export function validateReleaseMetadata(root = rootDefault(), adapter = "development") {
266
+ export function validateReleaseMetadata(root = rootDefault(), adapter = "development", packageOverride) {
267
267
  if (adapter === "stable") {
268
268
  const manifestPath = "packages/cli/shim/package.json";
269
269
  const shim = readJson(path.join(resolvePath(root), manifestPath));
@@ -279,7 +279,7 @@ export function validateReleaseMetadata(root = rootDefault(), adapter = "develop
279
279
  return validateStableShimGitRefProvenance(resolvePath(root), gitRef);
280
280
  }
281
281
  const errors = [];
282
- const snap = readReleaseMetadata(root);
282
+ const snap = readReleaseMetadata(root, packageOverride);
283
283
  const rootLabel = path.basename(resolvePath(root)) || "repo root";
284
284
  // 1. registry.json must publish a valid semver `skills[0].version`.
285
285
  if (snap.authoritativeVersion === null) {
@@ -373,7 +373,14 @@ export function releaseMetadataMain(opts = {}) {
373
373
  const root = resolvePath(opts.root ?? rootDefault());
374
374
  const out = opts.out ?? ((line) => process.stdout.write(line + "\n"));
375
375
  const err = opts.err ?? ((line) => process.stderr.write(line + "\n"));
376
- const errors = validateReleaseMetadata(root, opts.adapter);
376
+ const overrideVersion = process.env.AGENTERA_RELEASE_PACKAGE_VERSION;
377
+ const overrideGitRef = process.env.AGENTERA_RELEASE_GIT_REF;
378
+ if ((overrideVersion === undefined) !== (overrideGitRef === undefined)) {
379
+ out("release-metadata validation failed:");
380
+ out("- AGENTERA_RELEASE_PACKAGE_VERSION and AGENTERA_RELEASE_GIT_REF must be supplied together");
381
+ return 1;
382
+ }
383
+ const errors = validateReleaseMetadata(root, opts.adapter, overrideVersion === undefined ? undefined : { version: overrideVersion, gitRef: overrideGitRef });
377
384
  if (errors.length > 0) {
378
385
  out("release-metadata validation failed:");
379
386
  for (const message of errors)
@@ -1,4 +1,5 @@
1
1
  import { expanduser } from "../core/paths.js";
2
+ import { SOURCE_LABELS } from "../state/installRoot.js";
2
3
  import { resolvePlatformAppHome } from "./appModel.js";
3
4
  import { buildDoctorStatus } from "./doctor.js";
4
5
  /** Shared npx platform app-home resolution for prime and projectIntegration. */
@@ -6,7 +7,7 @@ export function resolveNpxPlatformStatus(opts) {
6
7
  const env = { ...process.env, ...(opts.env ?? {}), HOME: expanduser(opts.home) };
7
8
  const platformRoot = resolvePlatformAppHome(opts.home, env);
8
9
  const doctorOpts = {
9
- rootSource: "default",
10
+ rootSource: SOURCE_LABELS.default,
10
11
  sourceRoot: opts.sourceRoot,
11
12
  home: opts.home,
12
13
  project: opts.project,
@@ -65,10 +65,16 @@ function resolveIntegrationTargets(args) {
65
65
  project: args.project,
66
66
  env: args.env,
67
67
  });
68
+ // The executing npm bundle is already a complete app. A missing optional
69
+ // platform install therefore needs no integration work, while an existing
70
+ // stale or damaged platform install still retains its observed status.
71
+ const platformBundleStatus = platformStatus.rootStatus === "missing"
72
+ ? APP_UP_TO_DATE
73
+ : platformStatus.status;
68
74
  return {
69
75
  installRoot: platformRoot,
70
76
  bundleStatus: args.bundleStatus,
71
- platformBundleStatus: platformStatus.status,
77
+ platformBundleStatus,
72
78
  crossMajorBoundary: Boolean(platformStatus.crossMajorBoundary),
73
79
  crossMajorBoundaryDetected: Boolean(platformStatus.crossMajorBoundaryDetected),
74
80
  };
@@ -452,6 +452,12 @@ function createRecord(producerKind, artifactClass, artifactIdentity, artifactCon
452
452
  function finishEvidence(value) {
453
453
  return { ...value, evidenceDigest: observationDigest(value) };
454
454
  }
455
+ function packageManifestSummary(files) {
456
+ const normalized = [...files]
457
+ .map(({ path: file, size, mode }) => ({ path: file, size, mode }))
458
+ .sort((left, right) => left.path.localeCompare(right.path));
459
+ return { count: normalized.length, digest: observationDigest(normalized) };
460
+ }
455
461
  function packageRecord(root) {
456
462
  return loadRegistry(path.join(root, "references/adapters/package-registry.yaml"), root).get("agentera");
457
463
  }
@@ -639,6 +645,7 @@ export async function finalizePackageOwnerEvidence(options) {
639
645
  const record = packageRecord(path.join(runtimeRoot, "bundle"));
640
646
  const packageArtifact = packageArtifactObservation(runtimeRoot, fixture, options.requiredFiles, options.snapshotDirectory);
641
647
  const integrity = packageArtifact.integrity;
648
+ const secondManifestFiles = fixture.pathIndependence.secondManifest.files;
642
649
  const portability = {
643
650
  deterministicPackRuns: fixture.deterministicBytes.packRuns,
644
651
  deterministicTarballSha256: fixture.deterministicBytes.sha256,
@@ -655,7 +662,7 @@ export async function finalizePackageOwnerEvidence(options) {
655
662
  filename: fixture.pathIndependence.secondManifest.filename,
656
663
  integrity: fixture.pathIndependence.secondManifest.integrity,
657
664
  shasum: fixture.pathIndependence.secondManifest.shasum,
658
- files: fixture.pathIndependence.secondManifest.files,
665
+ files: packageManifestSummary(secondManifestFiles),
659
666
  },
660
667
  };
661
668
  const records = {
@@ -716,7 +723,7 @@ export function createGeneratedOwnerEvidence(options) {
716
723
  const bundleRoot = path.join(options.generationRoot, "bundle");
717
724
  const record = packageRecord(bundleRoot);
718
725
  const bootstrap = runtimeObservation.bootstrap;
719
- const generationFiles = [".agentera-generation.json", "dist/.agentera-generation.json", "bundle/.agentera-generation.json", "bundle/.agentera-npx-bundle.json", "bundle/extract-corpus-parity.json"]
726
+ const generationFiles = [".agentera-generation.json", "dist/.agentera-generation.json", "bundle/.agentera-generation.json", "dist/.agentera-build-source.json", "bundle/.agentera-build-source.json", "bundle/.agentera-npx-bundle.json", "bundle/extract-corpus-parity.json"]
720
727
  .filter((file) => fs.existsSync(path.join(options.generationRoot, file)));
721
728
  const records = {
722
729
  "capability.generated-modules": createRecord("generated-owner", "capability-generated-modules", "generation/dist/capabilities/*/instructions.js", capabilities.artifacts.modules, capabilities.modules, options.generation, null),
@@ -746,8 +753,15 @@ export function createGeneratedOwnerEvidence(options) {
746
753
  export function writeContentAddressedOwnerEvidence(directory, evidence) {
747
754
  const bytes = `${canonicalObservationJson(evidence)}\n`;
748
755
  const byteLength = Buffer.byteLength(bytes, "utf8");
749
- if (byteLength > OWNER_EVIDENCE_MAX_BYTES)
750
- throw new Error(`activation owner evidence exceeds ${OWNER_EVIDENCE_MAX_BYTES} bytes`);
756
+ if (byteLength > OWNER_EVIDENCE_MAX_BYTES) {
757
+ const contributors = Object.entries(evidence.records)
758
+ .map(([ref, record]) => [ref, Buffer.byteLength(canonicalObservationJson(record), "utf8")])
759
+ .sort((left, right) => right[1] - left[1])
760
+ .slice(0, 3)
761
+ .map(([ref, length]) => `${ref}=${length}`)
762
+ .join(", ");
763
+ throw new Error(`activation owner evidence is ${byteLength} bytes, over the ${OWNER_EVIDENCE_MAX_BYTES}-byte bound; largest records: ${contributors}`);
764
+ }
751
765
  fs.mkdirSync(directory, { recursive: true, mode: 0o700 });
752
766
  const name = `${evidence.producerKind}-${evidence.evidenceDigest}.json`;
753
767
  const target = path.join(directory, name);
@@ -185,6 +185,57 @@ export function createActivationEvidenceManifest(options) {
185
185
  };
186
186
  return { ...unsigned, manifestDigest: observationDigest(unsigned) };
187
187
  }
188
+ function deepFreeze(value, seen = new WeakSet()) {
189
+ if (!value || typeof value !== "object" || seen.has(value))
190
+ return value;
191
+ seen.add(value);
192
+ for (const child of Object.values(value))
193
+ deepFreeze(child, seen);
194
+ return Object.freeze(value);
195
+ }
196
+ /** Observe, snapshot, assemble, and validate immediate release evidence inside one trust boundary. */
197
+ export function assembleAndValidateActivationEvidence(context) {
198
+ let sourceObserverCalls = 0;
199
+ let generatedObserverCalls = 0;
200
+ sourceObserverCalls += 1;
201
+ const sourceEvidence = deepFreeze(structuredClone(context.sourceEvidence));
202
+ generatedObserverCalls += 1;
203
+ const observedGeneratedEvidence = createGeneratedOwnerEvidence({
204
+ root: context.root,
205
+ generationRoot: context.generationRoot,
206
+ generation: context.generation,
207
+ productionInputs: context.productionInputs,
208
+ });
209
+ const generatedEvidence = deepFreeze(structuredClone(observedGeneratedEvidence));
210
+ const productionEvidence = deepFreeze(structuredClone(context.productionEvidence));
211
+ const packageEvidence = deepFreeze(structuredClone(context.packageEvidence));
212
+ const expectedPackageIdentity = deepFreeze(structuredClone(context.expectedPackageIdentity));
213
+ const manifest = deepFreeze(createActivationEvidenceManifest({
214
+ root: context.root,
215
+ generation: context.generation,
216
+ productionEvidence,
217
+ sourceEvidence,
218
+ generatedEvidence,
219
+ packageEvidence,
220
+ }));
221
+ const violations = activationEvidenceViolationsInternal(manifest, {
222
+ root: context.root,
223
+ generationRoot: context.generationRoot,
224
+ generation: context.generation,
225
+ sourceEvidence,
226
+ generatedEvidence,
227
+ expectedPackageIdentity,
228
+ });
229
+ if (violations.length > 0)
230
+ throw new Error(violations[0]);
231
+ return {
232
+ manifest: structuredClone(manifest),
233
+ observerCalls: {
234
+ source: sourceObserverCalls,
235
+ generated: generatedObserverCalls,
236
+ },
237
+ };
238
+ }
188
239
  function validateOwnerEvidence(owner, producerKind, schemaVersion, sourceDigest, generation, packageIntegrity, violations) {
189
240
  if (!owner || owner.schemaVersion !== schemaVersion || owner.producerKind !== producerKind) {
190
241
  violations.push(`${producerKind} evidence schema or producer is wrong`);
@@ -326,12 +377,25 @@ function validateExecutedArtifactSemantics(manifest, violations) {
326
377
  violations.push("complete extracted package tree is not bound to the current tarball payload");
327
378
  }
328
379
  const portability = records.get("package.portability")?.content;
380
+ const extractedManifestFiles = Array.isArray(extractedEntries)
381
+ ? extractedEntries
382
+ .filter((entry) => entry.type === "file")
383
+ .map((entry) => ({ path: entry.path, size: entry.size, mode: entry.mode }))
384
+ .sort((left, right) => left.path.localeCompare(right.path))
385
+ : null;
386
+ const secondManifest = portability?.secondManifest;
329
387
  if (portability?.deterministicPackRuns !== 2
330
388
  || !/^[a-f0-9]{64}$/.test(portability?.deterministicTarballSha256 ?? "")
331
389
  || portability?.secondTarballSha256 !== portability?.deterministicTarballSha256
332
390
  || portability?.constructionRootCount !== 2 || portability?.constructionRootsDistinct !== true
333
391
  || portability?.extractedRootCount !== 1 || portability?.extractedRootsDistinct !== true
334
- || portability?.forbiddenPathMatches?.length !== 0 || !/^[a-f0-9]{64}$/.test(portability?.contentSha256 ?? "")) {
392
+ || portability?.forbiddenPathMatches?.length !== 0 || !/^[a-f0-9]{64}$/.test(portability?.contentSha256 ?? "")
393
+ || secondManifest?.filename !== manifest.packageArtifact.filename
394
+ || secondManifest?.integrity !== manifest.packageArtifact.integrity
395
+ || secondManifest?.shasum !== manifest.packageArtifact.shasum
396
+ || !Array.isArray(extractedManifestFiles)
397
+ || secondManifest?.files?.count !== extractedManifestFiles.length
398
+ || secondManifest?.files?.digest !== observationDigest(extractedManifestFiles)) {
335
399
  violations.push("extracted package portability evidence is incomplete or failed");
336
400
  }
337
401
  const sourceAuthority = records.get("bootstrap.source-authority")?.content;
@@ -453,15 +517,13 @@ function validateChecks(manifest, violations) {
453
517
  if (used.size !== Object.keys(PROVENANCE).length)
454
518
  violations.push("activation evidence contains unconsumed or multiply consumed producer records");
455
519
  }
456
- export function activationEvidenceViolations(actual, expectedOrContext) {
520
+ function activationEvidenceViolationsInternal(actual, context) {
457
521
  const manifest = actual;
458
522
  if (!manifest || typeof manifest !== "object")
459
523
  return ["activation evidence manifest is missing"];
460
524
  const violations = [];
461
- const context = "root" in expectedOrContext ? expectedOrContext : null;
462
- const expected = context ? null : expectedOrContext;
463
- const generation = context?.generation ?? expected.generation;
464
- const sourceDigest = context ? activationSourceDigest(context.root) : expected.currentSourceDigest;
525
+ const generation = context?.generation ?? manifest.generation;
526
+ const sourceDigest = context ? activationSourceDigest(context.root) : manifest.currentSourceDigest;
465
527
  const packageIntegrity = context
466
528
  ? context.expectedPackageIdentity?.packageArtifact?.integrity ?? null
467
529
  : manifest.packageArtifact?.integrity ?? null;
@@ -491,21 +553,8 @@ export function activationEvidenceViolations(actual, expectedOrContext) {
491
553
  if (Buffer.byteLength(`${canonicalObservationJson(manifest)}\n`, "utf8") > ACTIVATION_EVIDENCE_MAX_BYTES)
492
554
  violations.push("activation evidence manifest exceeds its byte bound");
493
555
  if (context) {
494
- const authoritativeSource = createSourceOwnerEvidence(context.root, context.productionInputs);
495
- const authoritativeGenerated = createGeneratedOwnerEvidence({
496
- root: context.root,
497
- generationRoot: context.generationRoot,
498
- generation: context.generation,
499
- productionInputs: context.productionInputs,
500
- });
501
- validateAuthoritativeOwner(manifest.producers?.source, authoritativeSource, violations);
502
- validateAuthoritativeOwner(manifest.producers?.generated, authoritativeGenerated, violations);
503
- if (!/^[a-f0-9]{64}$/.test(context.expectedManifestDigest)) {
504
- violations.push("trusted release activation evidence digest is missing or malformed");
505
- }
506
- else if (manifest.manifestDigest !== context.expectedManifestDigest) {
507
- violations.push("activation evidence manifest differs from the trusted release observation");
508
- }
556
+ validateAuthoritativeOwner(manifest.producers?.source, context.sourceEvidence, violations);
557
+ validateAuthoritativeOwner(manifest.producers?.generated, context.generatedEvidence, violations);
509
558
  violations.push(...activationPackageIdentityViolations(context.expectedPackageIdentity, manifest.producers?.package));
510
559
  for (const relative of [".agentera-generation.json", "dist/.agentera-generation.json", "bundle/.agentera-generation.json"]) {
511
560
  try {
@@ -517,8 +566,38 @@ export function activationEvidenceViolations(actual, expectedOrContext) {
517
566
  }
518
567
  }
519
568
  }
520
- if (expected && canonicalObservationJson(manifest) !== canonicalObservationJson(expected))
569
+ return [...new Set(violations)];
570
+ }
571
+ export function activationEvidenceManifestViolations(actual, expected) {
572
+ const violations = activationEvidenceViolationsInternal(actual, null);
573
+ if (canonicalObservationJson(actual) !== canonicalObservationJson(expected)) {
521
574
  violations.push("activation evidence differs from the expected independently observed manifest");
575
+ }
576
+ return [...new Set(violations)];
577
+ }
578
+ export function activationEvidenceViolations(actual, context) {
579
+ const sourceEvidence = createSourceOwnerEvidence(context.root, context.productionInputs);
580
+ const generatedEvidence = createGeneratedOwnerEvidence({
581
+ root: context.root,
582
+ generationRoot: context.generationRoot,
583
+ generation: context.generation,
584
+ productionInputs: context.productionInputs,
585
+ });
586
+ const violations = activationEvidenceViolationsInternal(actual, {
587
+ root: context.root,
588
+ generationRoot: context.generationRoot,
589
+ generation: context.generation,
590
+ sourceEvidence,
591
+ generatedEvidence,
592
+ expectedPackageIdentity: context.expectedPackageIdentity,
593
+ });
594
+ const manifest = actual;
595
+ if (!/^[a-f0-9]{64}$/.test(context.expectedManifestDigest)) {
596
+ violations.push("trusted release activation evidence digest is missing or malformed");
597
+ }
598
+ else if (manifest?.manifestDigest !== context.expectedManifestDigest) {
599
+ violations.push("activation evidence manifest differs from the trusted release observation");
600
+ }
522
601
  return [...new Set(violations)];
523
602
  }
524
603
  export function readActivationEvidenceManifest(generationRoot) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agentera",
3
- "version": "3.0.0-dev.72",
3
+ "version": "3.0.0-dev.75",
4
4
  "description": "One CLI, many capabilities: project state, artifact validation, and capability routing for AI coding agents",
5
5
  "keywords": [
6
6
  "agentera",
@@ -43,8 +43,6 @@
43
43
  "generated:cleanup": "node scripts/build-package.mjs --cleanup",
44
44
  "pack:dry-run": "node scripts/pack-package.mjs --dry-run",
45
45
  "release:prepare": "node scripts/publication-transaction.mjs prepare development",
46
- "release:prepare:push": "node scripts/publication-transaction.mjs prepare-push development",
47
- "release:validate:push": "node scripts/publication-transaction.mjs validate-push development",
48
46
  "release:qualify:source": "node scripts/release-qualification.mjs source",
49
47
  "release:ready": "node scripts/release-readiness.mjs development",
50
48
  "release:qualify:candidate": "node scripts/release-qualification.mjs candidate --adapter development",
@@ -82,6 +80,6 @@
82
80
  },
83
81
  "agentera": {
84
82
  "suiteVersion": "3.0.0",
85
- "gitRef": "35a1f8b54ec1670123401be27cad8a9432286863"
83
+ "gitRef": "01a0c460f2310bae5805a637973021af30960ee5"
86
84
  }
87
85
  }