agentera 3.0.0-dev.16 → 3.0.0-dev.18

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 (69) hide show
  1. package/README.md +9 -5
  2. package/bundle/CHANGELOG.md +12 -0
  3. package/bundle/README.md +6 -2
  4. package/bundle/UPGRADE.md +45 -6
  5. package/bundle/references/adapters/package-manifest-interface-model.yaml +0 -4
  6. package/bundle/references/adapters/package-surface-characterization.md +4 -4
  7. package/bundle/references/adapters/runtime-feature-parity.md +10 -2
  8. package/bundle/references/adapters/runtime-lifecycle-authority.yaml +17 -0
  9. package/bundle/references/adapters/runtime-lifecycle-operation-contract.yaml +1 -0
  10. package/bundle/skills/agentera/schemas/artifacts/plan.yaml +61 -1
  11. package/dist/cli/commands/prime/collectOrientationState.js +42 -33
  12. package/dist/cli/commands/prime/collectOrientationState.js.map +1 -1
  13. package/dist/cli/commands/schema.js +12 -3
  14. package/dist/cli/commands/schema.js.map +1 -1
  15. package/dist/cli/commands/state/plan.js +82 -46
  16. package/dist/cli/commands/state/plan.js.map +1 -1
  17. package/dist/cli/help.js +1 -1
  18. package/dist/cli/help.js.map +1 -1
  19. package/dist/cli/orientation/attention.js +99 -1
  20. package/dist/cli/orientation/attention.js.map +1 -1
  21. package/dist/cli/orientation.js +44 -17
  22. package/dist/cli/orientation.js.map +1 -1
  23. package/dist/cli/planArtifacts.js +95 -0
  24. package/dist/cli/planArtifacts.js.map +1 -0
  25. package/dist/cli/planEvidence.js +158 -0
  26. package/dist/cli/planEvidence.js.map +1 -0
  27. package/dist/cli/prime-blob.js +1 -1
  28. package/dist/cli/prime-blob.js.map +1 -1
  29. package/dist/cli/stateQuery.js +1 -0
  30. package/dist/cli/stateQuery.js.map +1 -1
  31. package/dist/hooks/validateArtifact/schema.js +8 -2
  32. package/dist/hooks/validateArtifact/schema.js.map +1 -1
  33. package/dist/registries/runtimeAdapterRegistry.js +4 -1
  34. package/dist/registries/runtimeAdapterRegistry.js.map +1 -1
  35. package/dist/release/releaseMetadata.js +1 -1
  36. package/dist/release/releaseMetadata.js.map +1 -1
  37. package/dist/runtime/lifecycleAdapterContract.js +63 -41
  38. package/dist/runtime/lifecycleAdapterContract.js.map +1 -1
  39. package/dist/runtime/lifecycleAdapters.js +162 -103
  40. package/dist/runtime/lifecycleAdapters.js.map +1 -1
  41. package/dist/runtime/lifecycleAuthority.js +81 -31
  42. package/dist/runtime/lifecycleAuthority.js.map +1 -1
  43. package/dist/runtime/lifecycleOperationContract.js +4 -0
  44. package/dist/runtime/lifecycleOperationContract.js.map +1 -1
  45. package/dist/runtime/lifecycleOperations.js +10 -2
  46. package/dist/runtime/lifecycleOperations.js.map +1 -1
  47. package/dist/runtime/lifecycleSnapshot.js +290 -25
  48. package/dist/runtime/lifecycleSnapshot.js.map +1 -1
  49. package/dist/runtime/retiredRuntimeCleanup.js +15 -9
  50. package/dist/runtime/retiredRuntimeCleanup.js.map +1 -1
  51. package/dist/upgrade/channels.js +6 -8
  52. package/dist/upgrade/channels.js.map +1 -1
  53. package/dist/upgrade/compatibility.js +5 -0
  54. package/dist/upgrade/compatibility.js.map +1 -1
  55. package/dist/upgrade/doctor.js +14 -13
  56. package/dist/upgrade/doctor.js.map +1 -1
  57. package/dist/upgrade/lifecycleUpgrade.js +68 -27
  58. package/dist/upgrade/lifecycleUpgrade.js.map +1 -1
  59. package/dist/upgrade/projectIntegration.js +148 -69
  60. package/dist/upgrade/projectIntegration.js.map +1 -1
  61. package/dist/upgrade/projectIntegrationDecision.js +99 -0
  62. package/dist/upgrade/projectIntegrationDecision.js.map +1 -1
  63. package/dist/upgrade/runtimeMigration.js +2 -2
  64. package/dist/upgrade/runtimeMigration.js.map +1 -1
  65. package/dist/upgrade/upgradeOrchestrator.js +21 -7
  66. package/dist/upgrade/upgradeOrchestrator.js.map +1 -1
  67. package/dist/validate/lifecycleAdapters.js +26 -2
  68. package/dist/validate/lifecycleAdapters.js.map +1 -1
  69. package/package.json +1 -1
@@ -2,7 +2,7 @@ import fs from "node:fs";
2
2
  import path from "node:path";
3
3
  import { buildRuntimeLifecycleState, loadLifecycleAuthority, } from "./lifecycleAuthority.js";
4
4
  import { RUNTIME_ADAPTER_CATEGORIES, loadRuntimeLifecycleAdapterContract, } from "./lifecycleAdapterContract.js";
5
- import { applyLifecycleOperations, createLifecycleOwnershipManifest, emptyLifecycleOwnershipLedger, planLifecycleOperations, } from "./lifecycleOperations.js";
5
+ import { applyLifecycleOperations, createLifecycleOwnershipManifest, emptyLifecycleOwnershipLedger, LIFECYCLE_MANUAL_REVIEW_GUIDANCE, planLifecycleOperations, } from "./lifecycleOperations.js";
6
6
  const STATE_PRIORITY = {
7
7
  blocked_unowned: 100,
8
8
  action_required: 95,
@@ -50,26 +50,41 @@ function expandResource(declaration, values) {
50
50
  if (declaration.kind === "directory_files") {
51
51
  let entries;
52
52
  try {
53
- entries = fs.readdirSync(sourcePath)
53
+ entries = fs
54
+ .readdirSync(sourcePath)
54
55
  .filter((name) => name.endsWith(declaration.extension))
55
56
  .sort();
56
57
  }
57
58
  catch (error) {
58
- return [{
59
+ return [
60
+ {
59
61
  declaration,
60
62
  sourcePath,
61
63
  destinationPath,
62
64
  sourceError: `could not read declared source directory: ${error.message}`,
63
- }];
65
+ },
66
+ ];
64
67
  }
65
68
  if (entries.length === 0) {
66
- return [{ declaration, sourcePath, destinationPath, sourceError: "declared source directory is empty" }];
69
+ return [
70
+ {
71
+ declaration,
72
+ sourcePath,
73
+ destinationPath,
74
+ sourceError: "declared source directory is empty",
75
+ },
76
+ ];
67
77
  }
68
78
  return entries.map((name) => {
69
79
  const source = path.join(sourcePath, name);
70
80
  const destination = path.join(destinationPath, name);
71
81
  if (source === destination) {
72
- return { declaration, sourcePath: source, destinationPath: destination, alreadyInPlace: true };
82
+ return {
83
+ declaration,
84
+ sourcePath: source,
85
+ destinationPath: destination,
86
+ alreadyInPlace: true,
87
+ };
73
88
  }
74
89
  try {
75
90
  return {
@@ -101,9 +116,17 @@ function expandResource(declaration, values) {
101
116
  }
102
117
  if (declaration.kind === "symlink") {
103
118
  if (!fs.existsSync(sourcePath)) {
104
- return [{ declaration, sourcePath, destinationPath, sourceError: "declared symlink source is missing" }];
119
+ return [
120
+ {
121
+ declaration,
122
+ sourcePath,
123
+ destinationPath,
124
+ sourceError: "declared symlink source is missing",
125
+ },
126
+ ];
105
127
  }
106
- return [{
128
+ return [
129
+ {
107
130
  declaration,
108
131
  sourcePath,
109
132
  destinationPath,
@@ -115,10 +138,12 @@ function expandResource(declaration, values) {
115
138
  linkTarget: sourcePath,
116
139
  required: declaration.required,
117
140
  },
118
- }];
141
+ },
142
+ ];
119
143
  }
120
144
  try {
121
- return [{
145
+ return [
146
+ {
122
147
  declaration,
123
148
  sourcePath,
124
149
  destinationPath,
@@ -130,26 +155,35 @@ function expandResource(declaration, values) {
130
155
  content: fs.readFileSync(sourcePath),
131
156
  required: declaration.required,
132
157
  },
133
- }];
158
+ },
159
+ ];
134
160
  }
135
161
  catch (error) {
136
- return [{
162
+ return [
163
+ {
137
164
  declaration,
138
165
  sourcePath,
139
166
  destinationPath,
140
167
  sourceError: `could not read declared source: ${error.message}`,
141
- }];
168
+ },
169
+ ];
142
170
  }
143
171
  }
144
172
  function nearestPlanState(operation) {
145
173
  switch (operation.action) {
146
- case "noop": return "confirmed";
147
- case "create": return "absent";
174
+ case "noop":
175
+ return "confirmed";
176
+ case "create":
177
+ return "absent";
148
178
  case "update":
149
- case "finalize_ownership": return "drifted";
150
- case "blocked_unowned": return "blocked_unowned";
151
- case "action_required": return operation.ownership === "unowned" ? "blocked_unowned" : "action_required";
152
- case "remove": return "action_required";
179
+ case "finalize_ownership":
180
+ return "drifted";
181
+ case "blocked_unowned":
182
+ return "blocked_unowned";
183
+ case "action_required":
184
+ return operation.ownership === "unowned" ? "blocked_unowned" : "action_required";
185
+ case "remove":
186
+ return "action_required";
153
187
  }
154
188
  }
155
189
  function probeSkill(location, canonical, surfaces) {
@@ -157,7 +191,13 @@ function probeSkill(location, canonical, surfaces) {
157
191
  try {
158
192
  const stat = fs.lstatSync(skillFile);
159
193
  if (!stat.isFile()) {
160
- return { location, canonical, surfaces, state: "drifted", detail: "SKILL.md is not a regular file" };
194
+ return {
195
+ location,
196
+ canonical,
197
+ surfaces,
198
+ state: "drifted",
199
+ detail: "SKILL.md is not a regular file",
200
+ };
161
201
  }
162
202
  const handle = fs.openSync(skillFile, "r");
163
203
  try {
@@ -165,9 +205,21 @@ function probeSkill(location, canonical, surfaces) {
165
205
  fs.readSync(handle, bytes, 0, bytes.length, 0);
166
206
  const text = bytes.toString("utf8");
167
207
  if (!/^name:\s*agentera\s*$/m.test(text)) {
168
- return { location, canonical, surfaces, state: "drifted", detail: "SKILL.md does not declare name: agentera" };
208
+ return {
209
+ location,
210
+ canonical,
211
+ surfaces,
212
+ state: "drifted",
213
+ detail: "SKILL.md does not declare name: agentera",
214
+ };
169
215
  }
170
- return { location, canonical, surfaces, state: "confirmed", detail: "Agentera SKILL.md detected" };
216
+ return {
217
+ location,
218
+ canonical,
219
+ surfaces,
220
+ state: "confirmed",
221
+ detail: "Agentera SKILL.md detected",
222
+ };
171
223
  }
172
224
  finally {
173
225
  fs.closeSync(handle);
@@ -175,7 +227,13 @@ function probeSkill(location, canonical, surfaces) {
175
227
  }
176
228
  catch (error) {
177
229
  if (error.code === "ENOENT") {
178
- return { location, canonical, surfaces, state: "absent", detail: "Agentera SKILL.md is absent" };
230
+ return {
231
+ location,
232
+ canonical,
233
+ surfaces,
234
+ state: "absent",
235
+ detail: "Agentera SKILL.md is absent",
236
+ };
179
237
  }
180
238
  return {
181
239
  location,
@@ -220,8 +278,7 @@ function evidenceState(value, category) {
220
278
  return "unknown";
221
279
  }
222
280
  function resourceReports(expanded, plan, category, surfaceId) {
223
- const matching = expanded.filter((resource) => resource.declaration.category === category
224
- && (resource.declaration.surfaceId === undefined || resource.declaration.surfaceId === surfaceId));
281
+ const matching = expanded.filter((resource) => resource.declaration.category === category && (resource.declaration.surfaceId === undefined || resource.declaration.surfaceId === surfaceId));
225
282
  const evidence = [];
226
283
  const states = [];
227
284
  const operations = [];
@@ -248,9 +305,7 @@ function resourceReports(expanded, plan, category, surfaceId) {
248
305
  });
249
306
  continue;
250
307
  }
251
- const operation = resource.operation
252
- ? plan.operations.find((candidate) => candidate.id === resource.operation?.id)
253
- : undefined;
308
+ const operation = resource.operation ? plan.operations.find((candidate) => candidate.id === resource.operation?.id) : undefined;
254
309
  const state = operation ? nearestPlanState(operation) : "unknown";
255
310
  states.push(state);
256
311
  if (operation)
@@ -269,22 +324,35 @@ function resourceReports(expanded, plan, category, surfaceId) {
269
324
  operations,
270
325
  };
271
326
  }
327
+ function hasObservedAgenteraIntegration(surfaceId, expanded, plan, skills) {
328
+ const resourceIntegration = expanded.some((resource) => {
329
+ if (resource.declaration.surfaceId !== surfaceId)
330
+ return false;
331
+ if (resource.alreadyInPlace)
332
+ return true;
333
+ const operation = resource.operation ? plan.operations.find((candidate) => candidate.id === resource.operation?.id) : undefined;
334
+ return operation !== undefined && operation.state !== "missing";
335
+ });
336
+ const skillIntegration = skills.some((skill) => !skill.canonical && skill.surfaces.includes(surfaceId) && !["absent", "unknown", "not_applicable"].includes(skill.state));
337
+ return resourceIntegration || skillIntegration;
338
+ }
339
+ function lifecycleSurfaceExpected(surface, hostPresence, expanded, plan, skills) {
340
+ return surface.presence === "required" || hostPresence === true || hasObservedAgenteraIntegration(surface.id, expanded, plan, skills);
341
+ }
272
342
  function actionRequiredSummary(operations) {
273
343
  const missingParentOperations = operations.filter((operation) => operation.reason === "allowed root is not an existing safe directory");
274
344
  if (missingParentOperations.length === operations.length) {
275
345
  const parents = [...new Set(missingParentOperations.map((operation) => path.dirname(operation.destination)))];
276
346
  return `Create the destination parent ${parents.join(", ")} as a real, non-symlink directory you control, then rerun preview; Agentera will not create or replace it.`;
277
347
  }
278
- return `Resolve these lifecycle blockers manually, then rerun preview: ${operations
279
- .map((operation) => `${operation.id}: ${operation.reason}`)
280
- .join("; ")}.`;
348
+ return `Resolve these lifecycle blockers manually, then rerun preview: ${operations.map((operation) => `${operation.id}: ${operation.reason}`).join("; ")}.`;
281
349
  }
282
350
  function remediationFor(state, claim, operations, nativeActions, runtimeId, surfaceId, evidencePath, sourceRoot) {
283
351
  const blockedOperations = operations.filter((operation) => operation.action === "blocked_unowned");
284
352
  if (blockedOperations.length > 0) {
285
353
  return {
286
354
  kind: "action_required",
287
- summary: "The destination is not ledger-owned; review the collision manually. Agentera will not adopt it by name or equality.",
355
+ summary: LIFECYCLE_MANUAL_REVIEW_GUIDANCE,
288
356
  operationIds: blockedOperations.map((operation) => operation.id),
289
357
  nativeActions: [],
290
358
  };
@@ -342,13 +410,16 @@ function remediationFor(state, claim, operations, nativeActions, runtimeId, surf
342
410
  return { kind: "action_required", summary, operationIds: [], nativeActions: [] };
343
411
  }
344
412
  if (state === "confirmed" || state === "not_applicable") {
345
- return { kind: "none", summary: "No remediation is required.", operationIds: [], nativeActions: [] };
413
+ return {
414
+ kind: "none",
415
+ summary: "No remediation is required.",
416
+ operationIds: [],
417
+ nativeActions: [],
418
+ };
346
419
  }
347
420
  return {
348
421
  kind: "unavailable",
349
- summary: claim.capability === "unsupported"
350
- ? "The host surface is explicitly unsupported."
351
- : "No verified safe remediation is declared for this host surface.",
422
+ summary: claim.capability === "unsupported" ? "The host surface is explicitly unsupported." : "No verified safe remediation is declared for this host surface.",
352
423
  operationIds: [],
353
424
  nativeActions: [],
354
425
  };
@@ -366,11 +437,8 @@ function assertSurfaceRemediationConsistency(category, state, operations, remedi
366
437
  throw new Error(`${category}: action-required operation must produce action_required reporting`);
367
438
  }
368
439
  if (remediation.kind === "repair") {
369
- const repairableIds = new Set(operations
370
- .filter((operation) => ["create", "update", "finalize_ownership"].includes(operation.action))
371
- .map((operation) => operation.id));
372
- if (remediation.operationIds.length === 0
373
- || remediation.operationIds.some((operationId) => !repairableIds.has(operationId))) {
440
+ const repairableIds = new Set(operations.filter((operation) => ["create", "update", "finalize_ownership"].includes(operation.action)).map((operation) => operation.id));
441
+ if (remediation.operationIds.length === 0 || remediation.operationIds.some((operationId) => !repairableIds.has(operationId))) {
374
442
  throw new Error(`${category}: repair remediation must reference only repairable plan operations`);
375
443
  }
376
444
  }
@@ -385,13 +453,20 @@ function categorySurface(runtimeId, surface, category, claim, expected, context,
385
453
  source: claim.evidence,
386
454
  required: false,
387
455
  diagnosisComplete: true,
388
- evidence: [{
456
+ evidence: [
457
+ {
389
458
  kind: "host_probe",
390
459
  state: "not_applicable",
391
460
  detail: "conditional host surface is absent",
392
461
  surfaceId: surface.id,
393
- }],
394
- remediation: { kind: "none", summary: "No remediation is required.", operationIds: [], nativeActions: [] },
462
+ },
463
+ ],
464
+ remediation: {
465
+ kind: "none",
466
+ summary: "No remediation is required.",
467
+ operationIds: [],
468
+ nativeActions: [],
469
+ },
395
470
  };
396
471
  }
397
472
  let state;
@@ -408,9 +483,7 @@ function categorySurface(runtimeId, surface, category, claim, expected, context,
408
483
  evidence = relevant.map((skill) => ({
409
484
  kind: "filesystem",
410
485
  state: skill.state,
411
- detail: skill.canonical
412
- ? `${skill.detail}; canonical shared location`
413
- : `${skill.detail}; additional discovery location can shadow or collide with canonical Agentera`,
486
+ detail: skill.canonical ? `${skill.detail}; canonical shared location` : `${skill.detail}; additional discovery location can shadow or collide with canonical Agentera`,
414
487
  path: skill.location,
415
488
  surfaceId: surface.id,
416
489
  }));
@@ -420,9 +493,7 @@ function categorySurface(runtimeId, surface, category, claim, expected, context,
420
493
  state = aggregateState([state, resources.state]);
421
494
  }
422
495
  else if (claim.capability === "repairable" || claim.evidence.startsWith("managed_resource:")) {
423
- const resourceCategory = claim.evidence.startsWith("managed_resource:")
424
- ? expanded.find((resource) => resource.declaration.id === claim.evidence.slice("managed_resource:".length))?.declaration.category ?? category
425
- : category;
496
+ const resourceCategory = claim.evidence.startsWith("managed_resource:") ? (expanded.find((resource) => resource.declaration.id === claim.evidence.slice("managed_resource:".length))?.declaration.category ?? category) : category;
426
497
  const resources = resourceReports(expanded, plan, resourceCategory, surface.id);
427
498
  state = resources.state;
428
499
  evidence = resources.evidence;
@@ -454,49 +525,59 @@ function categorySurface(runtimeId, surface, category, claim, expected, context,
454
525
  catch (error) {
455
526
  state = error.code === "ENOENT" ? "absent" : "unknown";
456
527
  }
457
- evidence = [{ kind: "filesystem", state, detail: claim.evidence, path: evidencePath, surfaceId: surface.id }];
528
+ evidence = [
529
+ {
530
+ kind: "filesystem",
531
+ state,
532
+ detail: claim.evidence,
533
+ path: evidencePath,
534
+ surfaceId: surface.id,
535
+ },
536
+ ];
458
537
  }
459
538
  else if (claim.evidence.startsWith("env:")) {
460
539
  const key = claim.evidence.slice("env:".length);
461
540
  const value = (context.env ?? process.env)[key];
462
541
  state = value ? "confirmed" : "absent";
463
- evidence = [{
542
+ evidence = [
543
+ {
464
544
  kind: "environment",
465
545
  state,
466
546
  detail: value ? `${key} is present` : `${key} is absent`,
467
547
  surfaceId: surface.id,
468
- }];
548
+ },
549
+ ];
469
550
  }
470
551
  else if (claim.evidence === "canonical_skill") {
471
552
  const canonical = skills.find((skill) => skill.canonical && skill.surfaces.includes(surface.id));
472
553
  state = canonical?.state ?? "unknown";
473
- evidence = [{
554
+ evidence = [
555
+ {
474
556
  kind: "filesystem",
475
557
  state,
476
558
  detail: canonical?.detail ?? "canonical skill was not diagnosed",
477
559
  path: canonical?.location,
478
560
  surfaceId: surface.id,
479
- }];
561
+ },
562
+ ];
480
563
  const resources = resourceReports(expanded, plan, "skills", surface.id);
481
564
  operations = resources.operations;
482
565
  evidence.push(...resources.evidence);
483
566
  state = aggregateState([state, resources.state]);
484
567
  }
485
568
  else {
486
- const override = context.categoryEvidence?.[category]?.[surface.id]
487
- ?? (category === "trust" ? context.surfaceEvidence?.[surface.id]?.trusted : undefined)
488
- ?? (category === "enablement" ? context.surfaceEvidence?.[surface.id]?.enabled : undefined);
569
+ const override = context.categoryEvidence?.[category]?.[surface.id] ?? (category === "trust" ? context.surfaceEvidence?.[surface.id]?.trusted : undefined) ?? (category === "enablement" ? context.surfaceEvidence?.[surface.id]?.enabled : undefined);
489
570
  state = evidenceState(override ?? "unknown", category);
490
- evidence = [{
571
+ evidence = [
572
+ {
491
573
  kind: "contract",
492
574
  state,
493
575
  detail: override === undefined ? `${claim.evidence}; no verified host observation was supplied` : claim.evidence,
494
576
  surfaceId: surface.id,
495
- }];
577
+ },
578
+ ];
496
579
  }
497
- const diagnosisComplete = claim.required
498
- ? state !== "unknown" && claim.capability !== "unverified"
499
- : true;
580
+ const diagnosisComplete = claim.required ? state !== "unknown" && claim.capability !== "unverified" : true;
500
581
  const remediation = remediationFor(state, claim, operations, nativeActions, runtimeId, surface.id, evidencePath, context.sourceRoot);
501
582
  assertSurfaceRemediationConsistency(category, state, operations, remediation);
502
583
  return {
@@ -517,7 +598,7 @@ function canonicalDetection(skills) {
517
598
  return "unknown";
518
599
  return canonical.state === "confirmed" ? true : false;
519
600
  }
520
- function lifecycleObservation(runtimeId, surfaces, hostPresence, categories, canonicalSkillDetected, diagnosisComplete, context) {
601
+ function lifecycleObservation(runtimeId, surfaces, hostPresence, expectedSurfaces, categories, canonicalSkillDetected, diagnosisComplete, context) {
521
602
  return {
522
603
  runtimeId,
523
604
  canonicalSkillDetected,
@@ -525,11 +606,9 @@ function lifecycleObservation(runtimeId, surfaces, hostPresence, categories, can
525
606
  unmetMandatoryFields: [],
526
607
  surfaces: surfaces.map((surface) => {
527
608
  const host = hostPresence[surface.id] ?? "unknown";
528
- const expected = surface.presence === "required" || host === true;
609
+ const expected = expectedSurfaces[surface.id] === true;
529
610
  const skill = categories.skills.surfaces.find((item) => item.surfaceId === surface.id);
530
- const trust = context.categoryEvidence?.trust?.[surface.id]
531
- ?? context.surfaceEvidence?.[surface.id]?.trusted
532
- ?? "unknown";
611
+ const trust = context.categoryEvidence?.trust?.[surface.id] ?? context.surfaceEvidence?.[surface.id]?.trusted ?? "unknown";
533
612
  let enabled;
534
613
  if (!expected)
535
614
  enabled = "not_applicable";
@@ -544,6 +623,7 @@ function lifecycleObservation(runtimeId, surfaces, hostPresence, categories, can
544
623
  enabled = "unknown";
545
624
  return {
546
625
  id: surface.id,
626
+ applicability: surface.presence === "required" ? "required" : expected ? "conditional" : "not_applicable",
547
627
  evidence: {
548
628
  host_present: host,
549
629
  installed: !expected ? "not_applicable" : canonicalSkillDetected,
@@ -581,11 +661,10 @@ export class RuntimeLifecycleAdapter {
581
661
  throw new Error(`${this.runtimeId}: missing declared resource ${id}`);
582
662
  return declaration;
583
663
  });
584
- const expanded = declarations.flatMap((declaration) => expandResource(declaration, values))
664
+ const expanded = declarations
665
+ .flatMap((declaration) => expandResource(declaration, values))
585
666
  .map((resource) => {
586
- if (resource.declaration.id !== "canonical_skill"
587
- || resource.operation?.kind !== "symlink"
588
- || !context.canonicalSkillTarget)
667
+ if (resource.declaration.id !== "canonical_skill" || resource.operation?.kind !== "symlink" || !context.canonicalSkillTarget)
589
668
  return resource;
590
669
  const target = path.resolve(context.canonicalSkillTarget);
591
670
  return {
@@ -594,12 +673,8 @@ export class RuntimeLifecycleAdapter {
594
673
  operation: { ...resource.operation, linkTarget: target },
595
674
  };
596
675
  });
597
- const operations = expanded.flatMap((resource) => resource.operation ? [resource.operation] : []);
598
- const allowedRoots = [...new Set([
599
- path.resolve(context.sourceRoot),
600
- ...(context.canonicalSkillTarget ? [path.resolve(context.canonicalSkillTarget, "..")] : []),
601
- ...expanded.map((resource) => path.dirname(resource.destinationPath)),
602
- ])];
676
+ const operations = expanded.flatMap((resource) => (resource.operation ? [resource.operation] : []));
677
+ const allowedRoots = [...new Set([path.resolve(context.sourceRoot), ...(context.canonicalSkillTarget ? [path.resolve(context.canonicalSkillTarget, "..")] : []), ...expanded.map((resource) => path.dirname(resource.destinationPath))])];
603
678
  const repairPlan = planLifecycleOperations({
604
679
  allowedRoots,
605
680
  operations,
@@ -609,17 +684,14 @@ export class RuntimeLifecycleAdapter {
609
684
  const skills = this.runtime.skillLocations.map((location) => probeSkill(expandTemplate(location.path, values), location.canonical, location.surfaces));
610
685
  const hostPresence = {};
611
686
  for (const surface of this.runtimeAuthority.surfaces) {
612
- hostPresence[surface.id] = context.surfaceEvidence?.[surface.id]?.host_present
613
- ?? probeBinary(this.runtime.binaries[surface.id], env);
687
+ hostPresence[surface.id] = context.surfaceEvidence?.[surface.id]?.host_present ?? probeBinary(this.runtime.binaries[surface.id], env);
614
688
  }
689
+ const expectedSurfaces = Object.fromEntries(this.runtimeAuthority.surfaces.map((surface) => [surface.id, lifecycleSurfaceExpected(surface, hostPresence[surface.id], expanded, repairPlan, skills)]));
615
690
  const categories = {};
616
691
  for (const category of RUNTIME_ADAPTER_CATEGORIES) {
617
692
  const surfaceReports = this.runtimeAuthority.surfaces.map((surface) => {
618
- const host = hostPresence[surface.id];
619
- const expected = surface.presence === "required" || host === true;
620
- return categorySurface(this.runtimeId, surface, category, this.runtime.categories[category][surface.id], expected, context, values, expanded, repairPlan, skills, category === "native_actions"
621
- ? this.runtime.nativeActions.filter((action) => action.surfaceId === surface.id)
622
- : []);
693
+ const expected = expectedSurfaces[surface.id] === true;
694
+ return categorySurface(this.runtimeId, surface, category, this.runtime.categories[category][surface.id], expected, context, values, expanded, repairPlan, skills, category === "native_actions" ? this.runtime.nativeActions.filter((action) => action.surfaceId === surface.id) : []);
623
695
  });
624
696
  categories[category] = {
625
697
  category,
@@ -631,12 +703,9 @@ export class RuntimeLifecycleAdapter {
631
703
  }
632
704
  const detected = canonicalDetection(skills);
633
705
  const requiredSkillReports = categories.skills.surfaces.filter((surface) => surface.required);
634
- const diagnosisComplete = detected !== "unknown"
635
- && requiredSkillReports.length > 0
636
- && requiredSkillReports.every((surface) => surface.diagnosisComplete);
637
- const observation = lifecycleObservation(this.runtimeId, this.runtimeAuthority.surfaces, hostPresence, categories, detected, diagnosisComplete, context);
638
- const authoritativeState = buildRuntimeLifecycleState(this.authority, [observation])
639
- .runtimes.find((runtime) => runtime.runtimeId === this.runtimeId);
706
+ const diagnosisComplete = detected !== "unknown" && requiredSkillReports.length > 0 && requiredSkillReports.every((surface) => surface.diagnosisComplete);
707
+ const observation = lifecycleObservation(this.runtimeId, this.runtimeAuthority.surfaces, hostPresence, expectedSurfaces, categories, detected, diagnosisComplete, context);
708
+ const authoritativeState = buildRuntimeLifecycleState(this.authority, [observation]).runtimes.find((runtime) => runtime.runtimeId === this.runtimeId);
640
709
  if (!authoritativeState)
641
710
  throw new Error(`${this.runtimeId}: lifecycle authority state is missing`);
642
711
  const supportFloor = {
@@ -645,12 +714,7 @@ export class RuntimeLifecycleAdapter {
645
714
  };
646
715
  const expectedCategorySurfaces = RUNTIME_ADAPTER_CATEGORIES.flatMap((category) => categories[category].surfaces.filter((surface) => surface.expected));
647
716
  const degraded = expectedCategorySurfaces.some((surface) => !["confirmed", "not_applicable"].includes(surface.state));
648
- const caveats = [
649
- ...this.contract.caveats,
650
- ...expanded
651
- .filter((resource) => resource.sourceError)
652
- .map((resource) => `${resource.declaration.id}: ${resource.sourceError}`),
653
- ];
717
+ const caveats = [...this.contract.caveats, ...expanded.filter((resource) => resource.sourceError).map((resource) => `${resource.declaration.id}: ${resource.sourceError}`)];
654
718
  return {
655
719
  schemaVersion: "agentera.runtimeAdapterReport.v1",
656
720
  runtimeId: this.runtimeId,
@@ -686,12 +750,7 @@ export class CopilotLifecycleAdapter extends RuntimeLifecycleAdapter {
686
750
  }
687
751
  }
688
752
  export function runtimeLifecycleAdapters(contract = loadRuntimeLifecycleAdapterContract(), authority = loadLifecycleAuthority()) {
689
- return [
690
- new OpenCodeLifecycleAdapter(contract, authority),
691
- new CodexLifecycleAdapter(contract, authority),
692
- new CursorLifecycleAdapter(contract, authority),
693
- new CopilotLifecycleAdapter(contract, authority),
694
- ];
753
+ return [new OpenCodeLifecycleAdapter(contract, authority), new CodexLifecycleAdapter(contract, authority), new CursorLifecycleAdapter(contract, authority), new CopilotLifecycleAdapter(contract, authority)];
695
754
  }
696
755
  export function inspectRuntimeLifecycleAdapters(context, contract = loadRuntimeLifecycleAdapterContract(), authority = loadLifecycleAuthority()) {
697
756
  const reports = runtimeLifecycleAdapters(contract, authority).map((adapter) => adapter.inspect(context));