ADR-1244 Phase 4 (D5 trust + D6 upgrade/compat). capability-trust.cjs (disclosure/consent, strict_known_registries, engines+compatVersions, reserved namespace) + capability-lifecycle.cjs (install/upgrade/remove/reconcile; ledger-as-commit-point _pending intent; atomic stage-then-swap; surgical marker-isolated shared-edit strip; owner-token lock) + capability-source promote/skipEnginesGate seams + loader pending-skip + config keys. No sandbox re-derived (consent+integrity+reversibility). 6 Codex adversarial rounds + /security-review (no HIGH) + /code-review; gsd-test green both platforms; CI green. Phase 5 (#1434) wires the CLI dispatch. Closes #1433. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -159,6 +159,27 @@ function overlayRoots(cwd: string, gsdHome?: string): Array<{ dir: string; scope
|
||||
return roots;
|
||||
}
|
||||
|
||||
/**
|
||||
* Read the per-scope ledger co-located with an overlay root (the root is `<scope>/.gsd/capabilities`,
|
||||
* so its ledger is `<scope>/.gsd-capabilities.json`) and return the ids carrying an in-flight
|
||||
* `_pending` intent — i.e. crashed/uncommitted installs or upgrades that must not be activated until
|
||||
* reconciliation completes. Never throws: a missing/invalid ledger yields an empty set.
|
||||
*/
|
||||
function pendingOverlayIds(rootDir: string): Set<string> {
|
||||
const out = new Set<string>();
|
||||
try {
|
||||
const ledgerPath = path.join(rootDir, '..', '..', '.gsd-capabilities.json');
|
||||
const parsed: unknown = JSON.parse(fs.readFileSync(ledgerPath, 'utf8'));
|
||||
if (!parsed || typeof parsed !== 'object') return out;
|
||||
const entries = (parsed as Record<string, unknown>)['entries'];
|
||||
if (!entries || typeof entries !== 'object' || Array.isArray(entries)) return out;
|
||||
for (const [id, entry] of Object.entries(entries as Record<string, unknown>)) {
|
||||
if (entry && typeof entry === 'object' && (entry as Record<string, unknown>)['_pending']) out.add(id);
|
||||
}
|
||||
} catch { /* missing/invalid ledger — nothing pending */ }
|
||||
return out;
|
||||
}
|
||||
|
||||
/** Shallow-attach overlay diagnostics WITHOUT mutating the frozen registry module. */
|
||||
function withOverlayMeta(reg: Registry, meta: OverlayMeta): Registry {
|
||||
return Object.assign({}, reg, { _overlay: meta });
|
||||
@@ -240,12 +261,22 @@ export function loadRegistry(options: LoadRegistryOptions = {}): Registry {
|
||||
} catch {
|
||||
continue; // no overlay dir at this scope — normal
|
||||
}
|
||||
// Ids whose ledger entry carries an in-flight `_pending` intent (a crashed/uncommitted
|
||||
// install or upgrade). They are NOT yet committed, so they must not be activated — reconcile
|
||||
// will roll them forward or back. Fail OPEN (skip without a gate block): an uncommitted gate
|
||||
// is not a real installed gate. See capability-lifecycle.cts (ADR-1244 Phase 4).
|
||||
const pendingIds = pendingOverlayIds(root.dir);
|
||||
for (const ent of entries) {
|
||||
if (!ent.isDirectory()) continue;
|
||||
const id = ent.name;
|
||||
const capDir = path.join(root.dir, id);
|
||||
const manifestPath = path.join(capDir, 'capability.json');
|
||||
|
||||
if (pendingIds.has(id)) {
|
||||
warnings.push({ id, scope: root.scope, reason: 'install/upgrade in progress (uncommitted) — deferred until reconciliation' });
|
||||
continue;
|
||||
}
|
||||
|
||||
let cap: CapManifest;
|
||||
try {
|
||||
cap = JSON.parse(fs.readFileSync(manifestPath, 'utf8')) as CapManifest;
|
||||
|
||||
Reference in New Issue
Block a user