copilot-tap-extension 2.0.8 → 2.0.9
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.
- package/README.md +2 -1
- package/SOUL.md +51 -0
- package/bin/install.mjs +2 -1
- package/dist/copilot-instructions.md +5 -0
- package/dist/extension.mjs +361 -20
- package/dist/version.json +1 -1
- package/docs/adr/0001-persistent-config-default-ownership.md +33 -0
- package/docs/adr/0002-local-provider-gateway-runtime-security.md +36 -0
- package/docs/adr/0003-emitter-delivery-lifecycle.md +68 -0
- package/docs/adr/0004-persistent-config-canonical-streams.md +86 -0
- package/docs/adr/0005-provider-sdk-push-and-dynamic-tools.md +48 -0
- package/docs/adr/0006-command-emitter-cwd-workspace-boundary.md +46 -0
- package/docs/adr/0007-runtime-session-workspace-context.md +62 -0
- package/docs/evals.md +41 -0
- package/docs/evolution-of-tap-icon.html +989 -0
- package/docs/providers.md +242 -0
- package/docs/recipes/adaptive-agent.md +303 -0
- package/docs/recipes/agent-brainstorm/100-extension-ideas.md +288 -0
- package/docs/recipes/agent-brainstorm/deep-ideas.md +216 -0
- package/docs/recipes/ambient-guardian.md +314 -0
- package/docs/recipes/browser-bridge.md +162 -0
- package/docs/recipes/codex-goals-for-tap-goal.md +136 -0
- package/docs/recipes/copilot-sdk-canvas.md +147 -0
- package/docs/recipes/deferred-cognition.md +310 -0
- package/docs/recipes/provider-integration-patterns.md +93 -0
- package/docs/recipes/provider-interface-advanced.md +1364 -0
- package/docs/recipes/provider-interface-core-profile.md +568 -0
- package/docs/recipes/tap-control-plane-roadmap.md +60 -0
- package/docs/recipes/universal-tool-gateway.md +202 -0
- package/docs/reference.md +229 -0
- package/docs/use-cases.md +348 -0
- package/package.json +4 -1
- package/providers/detour/README.md +84 -0
- package/providers/detour/bridge.js +219 -0
- package/providers/detour/index.mjs +322 -0
- package/providers/detour/package-lock.json +577 -0
- package/providers/detour/package.json +19 -0
- package/providers/detour/scripts/build.mjs +31 -0
- package/providers/detour/src/bridge.js +256 -0
- package/providers/detour/src/contracts.js +40 -0
- package/providers/detour/src/inspector.js +260 -0
- package/providers/detour/src/inspector.test.mjs +53 -0
- package/providers/detour/src/panel.js +465 -0
- package/providers/detour/src/provider-core.js +233 -0
- package/providers/detour/src/provider-core.test.mjs +185 -0
- package/providers/detour/src/react-context-core.js +143 -0
- package/providers/detour/src/react-context.js +44 -0
- package/providers/detour/src/react-context.test.mjs +41 -0
- package/providers/templates/README.md +23 -0
- package/providers/templates/ci-review-provider.mjs +46 -0
- package/providers/templates/detour-workflow-provider.mjs +41 -0
- package/providers/templates/jira-github-provider.mjs +42 -0
- package/providers/templates/provider-utils.mjs +45 -0
- package/providers/templates/sast-triage-provider.mjs +51 -0
package/dist/extension.mjs
CHANGED
|
@@ -4659,6 +4659,7 @@ function summarizeRuntimeState(state) {
|
|
|
4659
4659
|
const model = state?.model?.ok ? state.model.value : null;
|
|
4660
4660
|
const taskCount = state?.tasks?.ok ? state.tasks.value?.tasks?.length ?? 0 : null;
|
|
4661
4661
|
const scheduleCount = state?.schedules?.ok ? state.schedules.value?.entries?.length ?? 0 : null;
|
|
4662
|
+
const permissionCount = state?.permissions?.ok ? state.permissions.value?.items?.length ?? 0 : null;
|
|
4662
4663
|
const canvasCount = state?.openCanvases?.ok ? state.openCanvases.value?.openCanvases?.length ?? 0 : null;
|
|
4663
4664
|
return [
|
|
4664
4665
|
`sessionId=${state?.sessionId ?? "(none)"}`,
|
|
@@ -4666,6 +4667,7 @@ function summarizeRuntimeState(state) {
|
|
|
4666
4667
|
model ? `model=${model.modelId ?? "unknown"} reasoning=${model.reasoningEffort ?? "default"} context=${model.contextTier ?? "default"}` : null,
|
|
4667
4668
|
taskCount !== null ? `tasks=${taskCount}` : null,
|
|
4668
4669
|
scheduleCount !== null ? `schedules=${scheduleCount}` : null,
|
|
4670
|
+
permissionCount !== null ? `pendingPermissions=${permissionCount}` : null,
|
|
4669
4671
|
canvasCount !== null ? `openCanvases=${canvasCount}` : null,
|
|
4670
4672
|
`elicitation=${state?.capabilities?.ui?.elicitation === true ? "available" : "unavailable"}`,
|
|
4671
4673
|
`canvases=${state?.capabilities?.ui?.canvases === true ? "available" : "host-gated"}`
|
|
@@ -4714,6 +4716,64 @@ function createDiagnosticsTools(deps) {
|
|
|
4714
4716
|
return summarizeRuntimeState(state);
|
|
4715
4717
|
})
|
|
4716
4718
|
});
|
|
4719
|
+
if (typeof diagnostics2.setSessionMode === "function") {
|
|
4720
|
+
tools.push({
|
|
4721
|
+
name: "tap_set_session_mode",
|
|
4722
|
+
description: "Guarded Copilot mode switch for tap workflows. Requires explicit confirmation text and is intended for interactive/plan/autopilot transitions only.",
|
|
4723
|
+
parameters: {
|
|
4724
|
+
type: "object",
|
|
4725
|
+
properties: {
|
|
4726
|
+
mode: {
|
|
4727
|
+
type: "string",
|
|
4728
|
+
enum: ["interactive", "plan", "autopilot"],
|
|
4729
|
+
description: "Target Copilot session mode."
|
|
4730
|
+
},
|
|
4731
|
+
reason: {
|
|
4732
|
+
type: "string",
|
|
4733
|
+
description: "Why this mode switch is needed."
|
|
4734
|
+
},
|
|
4735
|
+
confirm: {
|
|
4736
|
+
type: "string",
|
|
4737
|
+
description: "Must be exactly 'set-session-mode' to confirm this user-visible mode change."
|
|
4738
|
+
}
|
|
4739
|
+
},
|
|
4740
|
+
required: ["mode", "reason", "confirm"]
|
|
4741
|
+
},
|
|
4742
|
+
handler: wrapToolHandler("tap_set_session_mode", (args) => ({ mode: args.mode }), async (args) => {
|
|
4743
|
+
if (args.confirm !== "set-session-mode") {
|
|
4744
|
+
throw new Error("Refusing to change session mode without confirm='set-session-mode'.");
|
|
4745
|
+
}
|
|
4746
|
+
const nextMode = await diagnostics2.setSessionMode(args.mode);
|
|
4747
|
+
return `Session mode set to ${nextMode}. reason=${args.reason}`;
|
|
4748
|
+
})
|
|
4749
|
+
});
|
|
4750
|
+
}
|
|
4751
|
+
}
|
|
4752
|
+
if (typeof diagnostics2.queryRecords === "function") {
|
|
4753
|
+
tools.push({
|
|
4754
|
+
name: "tap_query_records",
|
|
4755
|
+
description: "Reads structured tap records persisted in the session workspace, such as traces and stream-posts.",
|
|
4756
|
+
parameters: {
|
|
4757
|
+
type: "object",
|
|
4758
|
+
properties: {
|
|
4759
|
+
collection: {
|
|
4760
|
+
type: "string",
|
|
4761
|
+
description: "Record collection name, for example 'traces' or 'stream-posts'."
|
|
4762
|
+
},
|
|
4763
|
+
limit: {
|
|
4764
|
+
type: "integer",
|
|
4765
|
+
minimum: 1,
|
|
4766
|
+
maximum: 500,
|
|
4767
|
+
description: "Maximum recent records to return."
|
|
4768
|
+
}
|
|
4769
|
+
},
|
|
4770
|
+
required: ["collection"]
|
|
4771
|
+
},
|
|
4772
|
+
handler: wrapToolHandler("tap_query_records", (args) => ({ collection: args.collection }), async (args) => {
|
|
4773
|
+
const result = diagnostics2.queryRecords(args.collection, { limit: args.limit });
|
|
4774
|
+
return JSON.stringify(result, null, 2);
|
|
4775
|
+
})
|
|
4776
|
+
});
|
|
4717
4777
|
}
|
|
4718
4778
|
return tools;
|
|
4719
4779
|
}
|
|
@@ -6919,6 +6979,10 @@ function createProviderGateway(options = {}, adapters = {}) {
|
|
|
6919
6979
|
};
|
|
6920
6980
|
}
|
|
6921
6981
|
|
|
6982
|
+
// src/services/tap-runtime-service.mjs
|
|
6983
|
+
import fs4 from "node:fs";
|
|
6984
|
+
import path9 from "node:path";
|
|
6985
|
+
|
|
6922
6986
|
// src/session/listeners.mjs
|
|
6923
6987
|
var SESSION_ACTIVITY_EVENTS = [
|
|
6924
6988
|
"session.start",
|
|
@@ -8750,7 +8814,7 @@ function shouldPersistLoadedConfig(parsedConfig, normalizedConfig) {
|
|
|
8750
8814
|
return !isDeepStrictEqual(parsedConfig, serializeConfigForComparison(normalizedConfig));
|
|
8751
8815
|
}
|
|
8752
8816
|
function createConfigStore(options = {}) {
|
|
8753
|
-
const
|
|
8817
|
+
const fs5 = options.fs ?? { existsSync: existsSync2, readFileSync: readFileSync2, writeFileSync: writeFileSync2 };
|
|
8754
8818
|
const state = {
|
|
8755
8819
|
cwd: normalizeBaseCwd(options.cwd),
|
|
8756
8820
|
filePath: null,
|
|
@@ -8778,7 +8842,7 @@ function createConfigStore(options = {}) {
|
|
|
8778
8842
|
const exists = withConfigLoadPhase(
|
|
8779
8843
|
"checking config path",
|
|
8780
8844
|
"Unable to check whether the tap config file exists.",
|
|
8781
|
-
() =>
|
|
8845
|
+
() => fs5.existsSync(filePath),
|
|
8782
8846
|
{ filePath }
|
|
8783
8847
|
);
|
|
8784
8848
|
if (!exists) {
|
|
@@ -8787,7 +8851,7 @@ function createConfigStore(options = {}) {
|
|
|
8787
8851
|
const rawConfig = withConfigLoadPhase(
|
|
8788
8852
|
"reading config file",
|
|
8789
8853
|
"Unable to read the tap config file.",
|
|
8790
|
-
() =>
|
|
8854
|
+
() => fs5.readFileSync(filePath, "utf8"),
|
|
8791
8855
|
{ filePath }
|
|
8792
8856
|
);
|
|
8793
8857
|
const parsedConfig = withConfigLoadPhase(
|
|
@@ -8845,7 +8909,7 @@ function createConfigStore(options = {}) {
|
|
|
8845
8909
|
state.filePath = defaultConfigPath(state.cwd);
|
|
8846
8910
|
}
|
|
8847
8911
|
const payload = serializeConfig(state.config, LATEST_CONFIG_VERSION);
|
|
8848
|
-
|
|
8912
|
+
fs5.writeFileSync(state.filePath, `${JSON.stringify(payload, null, 2)}
|
|
8849
8913
|
`, "utf8");
|
|
8850
8914
|
}
|
|
8851
8915
|
function findStreamIndex(name) {
|
|
@@ -9351,21 +9415,46 @@ function createDefaultProcessAdapter() {
|
|
|
9351
9415
|
}
|
|
9352
9416
|
function recordScheduledTrace(emitter, context, { startedAt, endedAt = nowIso(), runIndex, result = null, error = null, consumedRun = true } = {}) {
|
|
9353
9417
|
try {
|
|
9418
|
+
const start = startedAt ?? endedAt;
|
|
9419
|
+
const status = result?.deferred ? "deferred" : result?.ok ? "success" : "failure";
|
|
9354
9420
|
context.diagnostics?.trace?.({
|
|
9355
|
-
traceId: `${stableTraceComponent(emitter.name)}-${Number(runIndex ?? emitter.runCount) || 0}-${Date.parse(
|
|
9421
|
+
traceId: `${stableTraceComponent(emitter.name)}-${Number(runIndex ?? emitter.runCount) || 0}-${Date.parse(start) || Date.now()}`,
|
|
9356
9422
|
emitterId: emitter.name,
|
|
9357
9423
|
emitterName: emitter.name,
|
|
9358
9424
|
runIndex: Number(runIndex ?? emitter.runCount) || null,
|
|
9359
9425
|
emitterType: emitter.emitterType,
|
|
9360
9426
|
runSchedule: emitter.runSchedule,
|
|
9361
|
-
startedAt:
|
|
9427
|
+
startedAt: start,
|
|
9362
9428
|
endedAt,
|
|
9363
|
-
status
|
|
9429
|
+
status,
|
|
9364
9430
|
ok: result?.ok === true,
|
|
9365
9431
|
consumedRun,
|
|
9366
9432
|
lineCount: emitter.lineCount,
|
|
9367
9433
|
droppedLineCount: emitter.droppedLineCount,
|
|
9368
9434
|
error: error ?? result?.error ?? null,
|
|
9435
|
+
spans: [
|
|
9436
|
+
{
|
|
9437
|
+
spanId: "emitter-run",
|
|
9438
|
+
kind: "emitter.run",
|
|
9439
|
+
name: emitter.name,
|
|
9440
|
+
startedAt: start,
|
|
9441
|
+
endedAt,
|
|
9442
|
+
status
|
|
9443
|
+
},
|
|
9444
|
+
{
|
|
9445
|
+
spanId: emitter.emitterType === EMITTER_TYPE.PROMPT ? "prompt-dispatch" : "command-process",
|
|
9446
|
+
parentSpanId: "emitter-run",
|
|
9447
|
+
kind: emitter.emitterType === EMITTER_TYPE.PROMPT ? "prompt.dispatch" : "command.process",
|
|
9448
|
+
name: emitter.emitterType,
|
|
9449
|
+
startedAt: start,
|
|
9450
|
+
endedAt,
|
|
9451
|
+
status,
|
|
9452
|
+
metadata: {
|
|
9453
|
+
stream: emitter.stream,
|
|
9454
|
+
cwd: emitter.cwd ?? null
|
|
9455
|
+
}
|
|
9456
|
+
}
|
|
9457
|
+
],
|
|
9369
9458
|
metadata: {
|
|
9370
9459
|
every: emitter.every ?? null,
|
|
9371
9460
|
everySchedule: emitter.everySchedule ?? null,
|
|
@@ -10558,9 +10647,21 @@ function createSessionPort(initialSession = null) {
|
|
|
10558
10647
|
tasks: await read("tasks", () => rpc.tasks?.list?.()),
|
|
10559
10648
|
schedules: await read("schedules", () => rpc.schedule?.list?.()),
|
|
10560
10649
|
skills: await read("skills", () => rpc.skills?.list?.()),
|
|
10650
|
+
permissions: await read("permissions", () => rpc.permissions?.pendingRequests?.()),
|
|
10561
10651
|
openCanvases: await read("openCanvases", () => rpc.canvas?.listOpen?.())
|
|
10562
10652
|
};
|
|
10563
10653
|
}
|
|
10654
|
+
async function setMode(mode) {
|
|
10655
|
+
if (!session2) {
|
|
10656
|
+
throw new LifecycleError("Session is not attached; cannot set mode.");
|
|
10657
|
+
}
|
|
10658
|
+
const modeApi = session2.rpc?.mode;
|
|
10659
|
+
if (!modeApi || typeof modeApi.set !== "function") {
|
|
10660
|
+
throw new LifecycleError("Session mode API is not available in this Copilot session.");
|
|
10661
|
+
}
|
|
10662
|
+
await modeApi.set({ mode });
|
|
10663
|
+
return modeApi.get();
|
|
10664
|
+
}
|
|
10564
10665
|
function registerTools(tools) {
|
|
10565
10666
|
if (!session2) return;
|
|
10566
10667
|
try {
|
|
@@ -10598,6 +10699,7 @@ function createSessionPort(initialSession = null) {
|
|
|
10598
10699
|
sendAndWait,
|
|
10599
10700
|
openCanvas,
|
|
10600
10701
|
getRuntimeState,
|
|
10702
|
+
setMode,
|
|
10601
10703
|
registerTools,
|
|
10602
10704
|
reloadExtension
|
|
10603
10705
|
};
|
|
@@ -10968,7 +11070,7 @@ function projectStream(stream) {
|
|
|
10968
11070
|
|
|
10969
11071
|
// src/services/stream-service.mjs
|
|
10970
11072
|
function createStreamService(deps) {
|
|
10971
|
-
const { streams, configStore, sessionPort, persist } = deps;
|
|
11073
|
+
const { streams, configStore, sessionPort, persist, recordStore = null } = deps;
|
|
10972
11074
|
function requireStreamChannel(channel, operation) {
|
|
10973
11075
|
return requireNormalizedName(channel, {
|
|
10974
11076
|
label: "Stream channel",
|
|
@@ -11012,6 +11114,17 @@ function createStreamService(deps) {
|
|
|
11012
11114
|
context: { channel: stream.name }
|
|
11013
11115
|
});
|
|
11014
11116
|
}
|
|
11117
|
+
try {
|
|
11118
|
+
recordStore?.appendRecord?.("stream-posts", {
|
|
11119
|
+
channel: stream.name,
|
|
11120
|
+
source,
|
|
11121
|
+
text,
|
|
11122
|
+
timestamp: appended.timestamp,
|
|
11123
|
+
monitorName: appended.monitorName ?? null,
|
|
11124
|
+
metadata: appended.metadata ?? null
|
|
11125
|
+
});
|
|
11126
|
+
} catch {
|
|
11127
|
+
}
|
|
11015
11128
|
void sessionPort.log(`Posted message to stream '${stream.name}'.`);
|
|
11016
11129
|
return { stream: projectStream(stream) };
|
|
11017
11130
|
}
|
|
@@ -11213,6 +11326,92 @@ function createGoalVerificationService({ getBaseCwd, getStreamHistory } = {}) {
|
|
|
11213
11326
|
return { verifyGoalOutput, auditClaims };
|
|
11214
11327
|
}
|
|
11215
11328
|
|
|
11329
|
+
// src/services/structured-record-store.mjs
|
|
11330
|
+
import fs3 from "node:fs";
|
|
11331
|
+
import path8 from "node:path";
|
|
11332
|
+
var DEFAULT_COLLECTION_LIMIT = 500;
|
|
11333
|
+
var COLLECTION_NAME_PATTERN = /^[a-z][a-z0-9-]{0,63}$/;
|
|
11334
|
+
function safeCollectionName(value) {
|
|
11335
|
+
const name = String(value ?? "").trim().toLowerCase();
|
|
11336
|
+
return COLLECTION_NAME_PATTERN.test(name) ? name : null;
|
|
11337
|
+
}
|
|
11338
|
+
function sessionWorkspacePath(sessionPort) {
|
|
11339
|
+
const session2 = typeof sessionPort?.current === "function" ? sessionPort.current() : null;
|
|
11340
|
+
return session2?.workspacePath ?? null;
|
|
11341
|
+
}
|
|
11342
|
+
function recordRoot(sessionPort) {
|
|
11343
|
+
const workspace = sessionWorkspacePath(sessionPort);
|
|
11344
|
+
if (!workspace) {
|
|
11345
|
+
return null;
|
|
11346
|
+
}
|
|
11347
|
+
return path8.join(workspace, "files", "tap-records");
|
|
11348
|
+
}
|
|
11349
|
+
function collectionPath(root, collection) {
|
|
11350
|
+
return path8.join(root, `${collection}.jsonl`);
|
|
11351
|
+
}
|
|
11352
|
+
function trimJsonlFile(filePath, maxRecords = DEFAULT_COLLECTION_LIMIT) {
|
|
11353
|
+
try {
|
|
11354
|
+
const lines = fs3.readFileSync(filePath, "utf8").split(/\r?\n/).filter(Boolean);
|
|
11355
|
+
if (lines.length <= maxRecords) {
|
|
11356
|
+
return;
|
|
11357
|
+
}
|
|
11358
|
+
fs3.writeFileSync(filePath, `${lines.slice(-maxRecords).join("\n")}
|
|
11359
|
+
`, "utf8");
|
|
11360
|
+
} catch {
|
|
11361
|
+
}
|
|
11362
|
+
}
|
|
11363
|
+
function createStructuredRecordStore({ sessionPort, maxRecords = DEFAULT_COLLECTION_LIMIT } = {}) {
|
|
11364
|
+
function appendRecord(collectionInput, record) {
|
|
11365
|
+
const collection = safeCollectionName(collectionInput);
|
|
11366
|
+
const root = recordRoot(sessionPort);
|
|
11367
|
+
if (!collection || !root) {
|
|
11368
|
+
return { stored: false, reason: !collection ? "invalid-collection" : "no-session-workspace" };
|
|
11369
|
+
}
|
|
11370
|
+
try {
|
|
11371
|
+
fs3.mkdirSync(root, { recursive: true });
|
|
11372
|
+
const filePath = collectionPath(root, collection);
|
|
11373
|
+
fs3.appendFileSync(filePath, `${JSON.stringify({ ...record, storedAt: (/* @__PURE__ */ new Date()).toISOString() })}
|
|
11374
|
+
`, "utf8");
|
|
11375
|
+
trimJsonlFile(filePath, maxRecords);
|
|
11376
|
+
return { stored: true, collection, path: filePath };
|
|
11377
|
+
} catch (error) {
|
|
11378
|
+
return { stored: false, collection, reason: error?.message ?? String(error ?? "unknown error") };
|
|
11379
|
+
}
|
|
11380
|
+
}
|
|
11381
|
+
function listRecords(collectionInput, options = {}) {
|
|
11382
|
+
const collection = safeCollectionName(collectionInput);
|
|
11383
|
+
const root = recordRoot(sessionPort);
|
|
11384
|
+
if (!collection || !root) {
|
|
11385
|
+
return { collection: collectionInput, records: [], available: false };
|
|
11386
|
+
}
|
|
11387
|
+
const limit = Math.max(1, Math.min(500, Math.floor(Number(options.limit ?? 50) || 50)));
|
|
11388
|
+
const filePath = collectionPath(root, collection);
|
|
11389
|
+
try {
|
|
11390
|
+
const records = fs3.readFileSync(filePath, "utf8").split(/\r?\n/).filter(Boolean).slice(-limit).map((line) => {
|
|
11391
|
+
try {
|
|
11392
|
+
return JSON.parse(line);
|
|
11393
|
+
} catch {
|
|
11394
|
+
return { parseError: true, raw: line };
|
|
11395
|
+
}
|
|
11396
|
+
});
|
|
11397
|
+
return { collection, path: filePath, available: true, records };
|
|
11398
|
+
} catch (error) {
|
|
11399
|
+
if (error?.code === "ENOENT") {
|
|
11400
|
+
return { collection, path: filePath, available: true, records: [] };
|
|
11401
|
+
}
|
|
11402
|
+
return { collection, path: filePath, available: false, records: [], error: error?.message ?? String(error) };
|
|
11403
|
+
}
|
|
11404
|
+
}
|
|
11405
|
+
function getRoot() {
|
|
11406
|
+
return recordRoot(sessionPort);
|
|
11407
|
+
}
|
|
11408
|
+
return {
|
|
11409
|
+
appendRecord,
|
|
11410
|
+
listRecords,
|
|
11411
|
+
getRoot
|
|
11412
|
+
};
|
|
11413
|
+
}
|
|
11414
|
+
|
|
11216
11415
|
// src/diagnostics/store.mjs
|
|
11217
11416
|
var DEFAULT_MAX_LOGS = 300;
|
|
11218
11417
|
var DEFAULT_MAX_EVENTS = 300;
|
|
@@ -11361,6 +11560,7 @@ function createDiagnosticsStore(options = {}) {
|
|
|
11361
11560
|
let traceCount = 0;
|
|
11362
11561
|
let cleanupSessionListener = () => {
|
|
11363
11562
|
};
|
|
11563
|
+
let recordSink = typeof options.recordSink === "function" ? options.recordSink : null;
|
|
11364
11564
|
function recordLog(source, message, options2 = {}) {
|
|
11365
11565
|
logCount += 1;
|
|
11366
11566
|
return logs.append({
|
|
@@ -11397,7 +11597,7 @@ function createDiagnosticsStore(options = {}) {
|
|
|
11397
11597
|
const startMs = Date.parse(startedAt);
|
|
11398
11598
|
const endMs = Date.parse(endedAt);
|
|
11399
11599
|
const durationMs = Number.isFinite(startMs) && Number.isFinite(endMs) ? Math.max(0, endMs - startMs) : null;
|
|
11400
|
-
|
|
11600
|
+
const entry = {
|
|
11401
11601
|
id: createId("trace", traceCount),
|
|
11402
11602
|
traceId: String(trace.traceId ?? `trace-${traceCount.toString(36)}`),
|
|
11403
11603
|
timestamp: endedAt,
|
|
@@ -11419,8 +11619,19 @@ function createDiagnosticsStore(options = {}) {
|
|
|
11419
11619
|
maxDepth: 3,
|
|
11420
11620
|
maxStringLength: 700,
|
|
11421
11621
|
maxCollectionItems: 20
|
|
11622
|
+
}),
|
|
11623
|
+
spans: safeClone(Array.isArray(trace.spans) ? trace.spans : [], {
|
|
11624
|
+
maxDepth: 4,
|
|
11625
|
+
maxStringLength: 700,
|
|
11626
|
+
maxCollectionItems: 30
|
|
11422
11627
|
})
|
|
11423
|
-
}
|
|
11628
|
+
};
|
|
11629
|
+
const appended = traces.append(entry);
|
|
11630
|
+
try {
|
|
11631
|
+
recordSink?.("traces", entry);
|
|
11632
|
+
} catch {
|
|
11633
|
+
}
|
|
11634
|
+
return appended;
|
|
11424
11635
|
}
|
|
11425
11636
|
function recordSessionEvent(event) {
|
|
11426
11637
|
sessionEventCount += 1;
|
|
@@ -11479,6 +11690,9 @@ function createDiagnosticsStore(options = {}) {
|
|
|
11479
11690
|
log: recordLog,
|
|
11480
11691
|
event: recordRuntimeEvent,
|
|
11481
11692
|
trace: recordTrace,
|
|
11693
|
+
setRecordSink: (nextSink) => {
|
|
11694
|
+
recordSink = typeof nextSink === "function" ? nextSink : null;
|
|
11695
|
+
},
|
|
11482
11696
|
attachSession,
|
|
11483
11697
|
detachSession,
|
|
11484
11698
|
snapshot
|
|
@@ -11506,6 +11720,33 @@ function projectToolForDiagnostics(tool) {
|
|
|
11506
11720
|
overridesBuiltInTool: tool.overridesBuiltInTool === true
|
|
11507
11721
|
};
|
|
11508
11722
|
}
|
|
11723
|
+
function collectEvalSummaries(baseCwd, limit = 20) {
|
|
11724
|
+
const root = path9.join(baseCwd, "evals", "results");
|
|
11725
|
+
const summaries = [];
|
|
11726
|
+
function walk(dir) {
|
|
11727
|
+
let entries = [];
|
|
11728
|
+
try {
|
|
11729
|
+
entries = fs4.readdirSync(dir, { withFileTypes: true });
|
|
11730
|
+
} catch {
|
|
11731
|
+
return;
|
|
11732
|
+
}
|
|
11733
|
+
for (const entry of entries) {
|
|
11734
|
+
const fullPath = path9.join(dir, entry.name);
|
|
11735
|
+
if (entry.isDirectory()) {
|
|
11736
|
+
walk(fullPath);
|
|
11737
|
+
} else if (entry.name === "summary.json") {
|
|
11738
|
+
try {
|
|
11739
|
+
const stat = fs4.statSync(fullPath);
|
|
11740
|
+
const parsed = JSON.parse(fs4.readFileSync(fullPath, "utf8"));
|
|
11741
|
+
summaries.push({ path: path9.relative(baseCwd, fullPath), modifiedAt: stat.mtime.toISOString(), summary: parsed });
|
|
11742
|
+
} catch {
|
|
11743
|
+
}
|
|
11744
|
+
}
|
|
11745
|
+
}
|
|
11746
|
+
}
|
|
11747
|
+
walk(root);
|
|
11748
|
+
return summaries.sort((left, right) => new Date(right.modifiedAt) - new Date(left.modifiedAt)).slice(0, limit);
|
|
11749
|
+
}
|
|
11509
11750
|
function createTapRuntimeService(options = {}) {
|
|
11510
11751
|
const diagnosticsStore = options.diagnostics ?? createDiagnosticsStore();
|
|
11511
11752
|
const {
|
|
@@ -11522,11 +11763,14 @@ function createTapRuntimeService(options = {}) {
|
|
|
11522
11763
|
...options,
|
|
11523
11764
|
diagnostics: diagnosticsStore
|
|
11524
11765
|
});
|
|
11766
|
+
const recordStore = createStructuredRecordStore({ sessionPort });
|
|
11767
|
+
diagnosticsStore.setRecordSink?.((collection, record) => recordStore.appendRecord(collection, record));
|
|
11525
11768
|
const streamService = createStreamService({
|
|
11526
11769
|
streams,
|
|
11527
11770
|
configStore,
|
|
11528
11771
|
sessionPort,
|
|
11529
|
-
persist
|
|
11772
|
+
persist,
|
|
11773
|
+
recordStore
|
|
11530
11774
|
});
|
|
11531
11775
|
const emitterService = createEmitterService({
|
|
11532
11776
|
streams,
|
|
@@ -11662,8 +11906,16 @@ function createTapRuntimeService(options = {}) {
|
|
|
11662
11906
|
void sessionPort.reloadExtension();
|
|
11663
11907
|
}
|
|
11664
11908
|
};
|
|
11665
|
-
function getDiagnosticsSnapshot(options2 = {}, extra = {}) {
|
|
11909
|
+
async function getDiagnosticsSnapshot(options2 = {}, extra = {}) {
|
|
11666
11910
|
const allTools = Array.isArray(extra.tools) ? extra.tools.map(projectToolForDiagnostics) : [];
|
|
11911
|
+
let sessionRuntime = null;
|
|
11912
|
+
try {
|
|
11913
|
+
sessionRuntime = await sessionPort.getRuntimeState();
|
|
11914
|
+
} catch (error) {
|
|
11915
|
+
sessionRuntime = {
|
|
11916
|
+
error: error?.message ?? String(error ?? "unknown error")
|
|
11917
|
+
};
|
|
11918
|
+
}
|
|
11667
11919
|
return {
|
|
11668
11920
|
generatedAt: nowIso(),
|
|
11669
11921
|
session: getSessionInfo(),
|
|
@@ -11677,7 +11929,9 @@ function createTapRuntimeService(options = {}) {
|
|
|
11677
11929
|
emitters: emitterCapabilities.listEmitters(),
|
|
11678
11930
|
gateway: extra.gateway ?? null,
|
|
11679
11931
|
tools: allTools,
|
|
11932
|
+
evals: collectEvalSummaries(sessionContext.getBaseCwd(), options2.evalLimit ?? 20),
|
|
11680
11933
|
notifications: typeof notifications.snapshot === "function" ? notifications.snapshot({ limit: options2.notificationLimit ?? 20 }) : null,
|
|
11934
|
+
sessionRuntime,
|
|
11681
11935
|
diagnostics: diagnosticsStore.snapshot(options2)
|
|
11682
11936
|
};
|
|
11683
11937
|
}
|
|
@@ -11697,6 +11951,9 @@ function createTapRuntimeService(options = {}) {
|
|
|
11697
11951
|
});
|
|
11698
11952
|
},
|
|
11699
11953
|
getSessionRuntimeState: () => sessionPort.getRuntimeState(),
|
|
11954
|
+
setSessionMode: (mode) => sessionPort.setMode(mode),
|
|
11955
|
+
queryRecords: (collection, options2 = {}) => recordStore.listRecords(collection, options2),
|
|
11956
|
+
getRecordRoot: () => recordStore.getRoot(),
|
|
11700
11957
|
attachSession: diagnosticsStore.attachSession,
|
|
11701
11958
|
detachSession: diagnosticsStore.detachSession
|
|
11702
11959
|
};
|
|
@@ -12004,7 +12261,7 @@ function createHtml() {
|
|
|
12004
12261
|
overflow: auto;
|
|
12005
12262
|
}
|
|
12006
12263
|
|
|
12007
|
-
.stream-grid, .emitter-grid, .provider-grid, .trace-grid {
|
|
12264
|
+
.stream-grid, .emitter-grid, .provider-grid, .trace-grid, .session-grid {
|
|
12008
12265
|
display: grid;
|
|
12009
12266
|
gap: 12px;
|
|
12010
12267
|
}
|
|
@@ -12186,6 +12443,26 @@ function createHtml() {
|
|
|
12186
12443
|
</div>
|
|
12187
12444
|
</section>
|
|
12188
12445
|
|
|
12446
|
+
<section class="panel">
|
|
12447
|
+
<div class="panel-head">
|
|
12448
|
+
<h2>Session control</h2>
|
|
12449
|
+
<small id="session-state">unknown</small>
|
|
12450
|
+
</div>
|
|
12451
|
+
<div class="panel-body">
|
|
12452
|
+
<div class="session-grid" id="session-control"></div>
|
|
12453
|
+
</div>
|
|
12454
|
+
</section>
|
|
12455
|
+
|
|
12456
|
+
<section class="panel">
|
|
12457
|
+
<div class="panel-head">
|
|
12458
|
+
<h2>Eval gate</h2>
|
|
12459
|
+
<small id="eval-count">0 summaries</small>
|
|
12460
|
+
</div>
|
|
12461
|
+
<div class="panel-body">
|
|
12462
|
+
<div class="session-grid" id="evals"></div>
|
|
12463
|
+
</div>
|
|
12464
|
+
</section>
|
|
12465
|
+
|
|
12189
12466
|
<section class="panel timeline-panel">
|
|
12190
12467
|
<div class="panel-head">
|
|
12191
12468
|
<h2>Evidence timeline</h2>
|
|
@@ -12223,6 +12500,8 @@ function createHtml() {
|
|
|
12223
12500
|
const providers = snapshot.gateway?.providers?.length ?? 0;
|
|
12224
12501
|
const queue = snapshot.notifications?.queueSize ?? 0;
|
|
12225
12502
|
const traces = snapshot.diagnostics?.stats?.traces?.retained ?? 0;
|
|
12503
|
+
const tasks = snapshot.sessionRuntime?.tasks?.ok ? (snapshot.sessionRuntime.tasks.value?.tasks?.length ?? 0) : 0;
|
|
12504
|
+
const evals = snapshot.evals?.length ?? 0;
|
|
12226
12505
|
const sessionEvents = snapshot.diagnostics?.stats?.sessionEvents?.retained ?? 0;
|
|
12227
12506
|
el("metrics").innerHTML = [
|
|
12228
12507
|
metric("streams", streams),
|
|
@@ -12230,6 +12509,8 @@ function createHtml() {
|
|
|
12230
12509
|
metric("providers", providers),
|
|
12231
12510
|
metric("queued injections", queue),
|
|
12232
12511
|
metric("traces", traces),
|
|
12512
|
+
metric("tasks", tasks),
|
|
12513
|
+
metric("eval summaries", evals),
|
|
12233
12514
|
metric("configured emitters", configured),
|
|
12234
12515
|
metric("session events", sessionEvents)
|
|
12235
12516
|
].join("");
|
|
@@ -12269,6 +12550,28 @@ function createHtml() {
|
|
|
12269
12550
|
el("providers").innerHTML = gatewayRecord + rows;
|
|
12270
12551
|
}
|
|
12271
12552
|
|
|
12553
|
+
function extractMarkedJson(text, marker) {
|
|
12554
|
+
const raw = String(text ?? "");
|
|
12555
|
+
const start = "=== BEGIN_" + marker + " ===";
|
|
12556
|
+
const end = "=== END_" + marker + " ===";
|
|
12557
|
+
const startIndex = raw.indexOf(start);
|
|
12558
|
+
const endIndex = raw.indexOf(end);
|
|
12559
|
+
if (startIndex < 0 || endIndex < 0 || endIndex <= startIndex) return null;
|
|
12560
|
+
const body = raw.slice(startIndex + start.length, endIndex).trim();
|
|
12561
|
+
try { return JSON.parse(body); } catch { return null; }
|
|
12562
|
+
}
|
|
12563
|
+
|
|
12564
|
+
function collectReviewRecords(snapshot) {
|
|
12565
|
+
const records = [];
|
|
12566
|
+
for (const stream of snapshot.streams ?? []) {
|
|
12567
|
+
for (const entry of stream.entries ?? []) {
|
|
12568
|
+
const parsed = extractMarkedJson(entry.text, "REVIEW_RECORD");
|
|
12569
|
+
if (parsed) records.push({ stream: stream.name, timestamp: entry.timestamp, ...parsed });
|
|
12570
|
+
}
|
|
12571
|
+
}
|
|
12572
|
+
return records.sort((a, b) => new Date(b.timestamp) - new Date(a.timestamp));
|
|
12573
|
+
}
|
|
12574
|
+
|
|
12272
12575
|
function renderTraces(snapshot) {
|
|
12273
12576
|
const traces = snapshot.diagnostics?.traces ?? [];
|
|
12274
12577
|
const goalStreams = (snapshot.streams ?? []).filter((stream) => String(stream.name ?? "").startsWith("goal-"));
|
|
@@ -12281,7 +12584,36 @@ function createHtml() {
|
|
|
12281
12584
|
const latest = (stream.entries ?? []).slice(-2).reverse().map((entry) => '<div class="entry"><span class="meta">' + escapeHtml(timeOnly(entry.timestamp)) + '</span>\\n' + escapeHtml(entry.text) + '</div>').join("");
|
|
12282
12585
|
return '<div class="record"><strong>' + escapeHtml(stream.name) + ' <span class="badge info">goal stream</span></strong><div class="meta">' + escapeHtml(stream.entries?.length ?? 0) + ' retained entries</div>' + (latest || '<div class="entry">No goal ledger entries retained.</div>') + '</div>';
|
|
12283
12586
|
}).join("");
|
|
12284
|
-
|
|
12587
|
+
const reviewRecords = collectReviewRecords(snapshot).slice(0, 8).map((record) => '<div class="record"><strong>' + escapeHtml(record.stream) + ' <span class="badge info">' + escapeHtml(record.issue_type ?? "review") + '</span></strong><div class="meta">entries=' + escapeHtml(record.entries_examined ?? "?") + ' | reviewed=' + escapeHtml(timeOnly(record.reviewed_at ?? record.timestamp)) + '</div><div class="details">' + escapeHtml(compactJson({ patterns_changed: record.patterns_changed ?? [], remaining_noise_delta: record.remaining_noise_delta ?? [] })) + '</div></div>').join("");
|
|
12588
|
+
el("traces").innerHTML = (traceRows || '<div class="empty">No emitter-run traces retained yet.</div>') + (goalRows ? '<div class="panel-head" style="margin:14px -14px 12px"><h2>Goal ledgers</h2><small>' + goalStreams.length + ' streams</small></div>' + goalRows : "") + (reviewRecords ? '<div class="panel-head" style="margin:14px -14px 12px"><h2>Monitor reviews</h2><small>' + collectReviewRecords(snapshot).length + ' records</small></div>' + reviewRecords : "");
|
|
12589
|
+
}
|
|
12590
|
+
|
|
12591
|
+
function renderSessionControl(snapshot) {
|
|
12592
|
+
const runtime = snapshot.sessionRuntime ?? {};
|
|
12593
|
+
const mode = runtime.mode?.ok ? runtime.mode.value : "unknown";
|
|
12594
|
+
const model = runtime.model?.ok ? runtime.model.value : null;
|
|
12595
|
+
const tasks = runtime.tasks?.ok ? (runtime.tasks.value?.tasks ?? []) : [];
|
|
12596
|
+
const schedules = runtime.schedules?.ok ? (runtime.schedules.value?.entries ?? []) : [];
|
|
12597
|
+
el("session-state").textContent = String(mode);
|
|
12598
|
+
const stateRecord = '<div class="record"><strong>session <span class="badge info">' + escapeHtml(mode) + '</span></strong><div class="meta">model=' + escapeHtml(model?.modelId ?? "unknown") + ' | reasoning=' + escapeHtml(model?.reasoningEffort ?? "default") + ' | schedules=' + escapeHtml(schedules.length) + '</div></div>';
|
|
12599
|
+
const taskRows = tasks.slice(0, 10).map((task) => '<div class="record"><strong>' + escapeHtml(task.description ?? task.id ?? "task") + ' <span class="badge ' + escapeHtml(task.status ?? "info") + '">' + escapeHtml(task.status ?? "?") + '</span></strong><div class="meta">' + escapeHtml(task.type ?? "task") + ' | id=' + escapeHtml(task.id ?? "?") + ' | mode=' + escapeHtml(task.executionMode ?? "?") + '</div></div>').join("");
|
|
12600
|
+
el("session-control").innerHTML = stateRecord + (taskRows || '<div class="empty">No native Copilot tasks are currently tracked.</div>');
|
|
12601
|
+
}
|
|
12602
|
+
|
|
12603
|
+
function renderEvals(snapshot) {
|
|
12604
|
+
const evals = snapshot.evals ?? [];
|
|
12605
|
+
el("eval-count").textContent = evals.length + " summaries";
|
|
12606
|
+
if (evals.length === 0) {
|
|
12607
|
+
el("evals").innerHTML = '<div class="empty">No eval summaries found under evals/results.</div>';
|
|
12608
|
+
return;
|
|
12609
|
+
}
|
|
12610
|
+
el("evals").innerHTML = evals.slice(0, 12).map((item) => {
|
|
12611
|
+
const summary = item.summary ?? {};
|
|
12612
|
+
const caseId = summary.caseId ?? summary.mode ?? "run";
|
|
12613
|
+
const verdict = summary.verdict?.verdict ?? (Array.isArray(summary) ? "batch" : "unknown");
|
|
12614
|
+
const tone = verdict === "pass" || summary.extensionToolAvailable === true ? "ready" : verdict === "fail" ? "error" : "info";
|
|
12615
|
+
return '<div class="record"><strong>' + escapeHtml(caseId) + ' <span class="badge ' + tone + '">' + escapeHtml(verdict) + '</span></strong><div class="meta">' + escapeHtml(item.path) + ' | ' + escapeHtml(timeOnly(item.modifiedAt)) + '</div><div class="details">' + escapeHtml(summary.verdict?.summary ?? summary.detail ?? "") + '</div></div>';
|
|
12616
|
+
}).join("");
|
|
12285
12617
|
}
|
|
12286
12618
|
|
|
12287
12619
|
function collectTimeline(snapshot) {
|
|
@@ -12327,6 +12659,8 @@ function createHtml() {
|
|
|
12327
12659
|
renderStreams(snapshot);
|
|
12328
12660
|
renderProviders(snapshot);
|
|
12329
12661
|
renderTraces(snapshot);
|
|
12662
|
+
renderSessionControl(snapshot);
|
|
12663
|
+
renderEvals(snapshot);
|
|
12330
12664
|
renderTimeline(snapshot);
|
|
12331
12665
|
}
|
|
12332
12666
|
|
|
@@ -12375,7 +12709,7 @@ function createHtml() {
|
|
|
12375
12709
|
}
|
|
12376
12710
|
function createTapDiagnosticsCanvas({ getSnapshot, diagnostics: diagnostics2 } = {}) {
|
|
12377
12711
|
const instances = /* @__PURE__ */ new Map();
|
|
12378
|
-
function snapshot(options = {}) {
|
|
12712
|
+
async function snapshot(options = {}) {
|
|
12379
12713
|
return typeof getSnapshot === "function" ? getSnapshot(sanitizeSnapshotOptions(options)) : { generatedAt: (/* @__PURE__ */ new Date()).toISOString(), error: "No diagnostics snapshot provider configured." };
|
|
12380
12714
|
}
|
|
12381
12715
|
function log(message, level = "info", metadata = {}) {
|
|
@@ -12389,7 +12723,7 @@ function createTapDiagnosticsCanvas({ getSnapshot, diagnostics: diagnostics2 } =
|
|
|
12389
12723
|
return;
|
|
12390
12724
|
}
|
|
12391
12725
|
if (url.pathname === "/api/snapshot") {
|
|
12392
|
-
|
|
12726
|
+
void snapshot({ limit: url.searchParams.get("limit") }).then((data) => jsonResponse(res, 200, data));
|
|
12393
12727
|
return;
|
|
12394
12728
|
}
|
|
12395
12729
|
if (url.pathname === "/events") {
|
|
@@ -12400,10 +12734,17 @@ function createTapDiagnosticsCanvas({ getSnapshot, diagnostics: diagnostics2 } =
|
|
|
12400
12734
|
"X-Accel-Buffering": "no"
|
|
12401
12735
|
});
|
|
12402
12736
|
const send = () => {
|
|
12403
|
-
|
|
12404
|
-
|
|
12737
|
+
void snapshot().then((data) => {
|
|
12738
|
+
res.write(`event: snapshot
|
|
12739
|
+
data: ${JSON.stringify(data)}
|
|
12740
|
+
|
|
12741
|
+
`);
|
|
12742
|
+
}).catch((error) => {
|
|
12743
|
+
res.write(`event: snapshot
|
|
12744
|
+
data: ${JSON.stringify({ error: error?.message ?? String(error) })}
|
|
12405
12745
|
|
|
12406
12746
|
`);
|
|
12747
|
+
});
|
|
12407
12748
|
};
|
|
12408
12749
|
send();
|
|
12409
12750
|
const interval = setInterval(send, DEFAULT_REFRESH_MS);
|
|
@@ -12454,7 +12795,7 @@ data: ${JSON.stringify(snapshot())}
|
|
|
12454
12795
|
},
|
|
12455
12796
|
handler: async ({ input }) => ({
|
|
12456
12797
|
ok: true,
|
|
12457
|
-
summary: summarizeSnapshot(snapshot(input))
|
|
12798
|
+
summary: summarizeSnapshot(await snapshot(input))
|
|
12458
12799
|
})
|
|
12459
12800
|
},
|
|
12460
12801
|
{
|
|
@@ -12530,7 +12871,7 @@ function createCopilotChannelsRuntime(options = {}) {
|
|
|
12530
12871
|
log: runtimeService.provider.log
|
|
12531
12872
|
});
|
|
12532
12873
|
logRuntime("gateway created");
|
|
12533
|
-
function getDiagnosticSnapshot(options2 = {}) {
|
|
12874
|
+
async function getDiagnosticSnapshot(options2 = {}) {
|
|
12534
12875
|
return runtimeService.diagnostics.snapshot(options2, {
|
|
12535
12876
|
gateway: typeof gateway.getDiagnosticState === "function" ? gateway.getDiagnosticState() : null,
|
|
12536
12877
|
tools: gateway.isRunning() ? gateway.getAllTools(tools) : tools
|
package/dist/version.json
CHANGED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# ADR 0001: Persistent config emitters default to user ownership
|
|
2
|
+
|
|
3
|
+
## Status
|
|
4
|
+
|
|
5
|
+
Accepted
|
|
6
|
+
|
|
7
|
+
## Context
|
|
8
|
+
|
|
9
|
+
No `docs/adr/0000-template.md` existed when this ADR was written, so this file uses the standard ADR sections.
|
|
10
|
+
|
|
11
|
+
The project documentation treats on-disk emitter definitions as persistent workflows and recommends `userOwned` ownership for persistent, recurring, or policy-bearing emitters. Existing normalization code defaulted missing persisted emitter ownership and lifespan to `modelOwned` and `temporary`, even though configured emitters are restored from disk and listed as persistent definitions.
|
|
12
|
+
|
|
13
|
+
That mismatch can weaken the protection model for manually edited config files: an emitter with no explicit ownership may be treated as model-owned during normalization even though it came from persistent user config.
|
|
14
|
+
|
|
15
|
+
## Decision
|
|
16
|
+
|
|
17
|
+
Persisted/on-disk emitter definitions default to:
|
|
18
|
+
|
|
19
|
+
- `ownership: "userOwned"`
|
|
20
|
+
- `lifespan: "persistent"`
|
|
21
|
+
|
|
22
|
+
Normalization still honors explicit compatibility fields:
|
|
23
|
+
|
|
24
|
+
- `ownership` or legacy `managedBy`, including explicit `modelOwned`
|
|
25
|
+
- `lifespan` or legacy `scope`, including explicit `temporary`
|
|
26
|
+
|
|
27
|
+
Runtime-created temporary/model-owned emitters keep their explicit normalized fields when serialized, so this default only applies when persisted config omits ownership/lifespan signals.
|
|
28
|
+
|
|
29
|
+
## Consequences
|
|
30
|
+
|
|
31
|
+
- Manually authored config aligns with the documented safety default for persistent workflows.
|
|
32
|
+
- Legacy config with explicit `modelOwned` or `temporary` remains compatible.
|
|
33
|
+
- Future changes that alter config ownership/lifespan defaults should update or supersede this ADR.
|