@devrouter/cli 0.0.80 → 0.1.0
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/dist/devrouter-lifecycle-worker.js +2 -2
- package/dist/devrouter.js +71 -22
- package/package.json +3 -1
- package/upgrade-prompts/0.1.0.md +69 -0
|
@@ -5347,7 +5347,7 @@ function retain(session, previous) {
|
|
|
5347
5347
|
|
|
5348
5348
|
// src/core/reliability-operation-store.ts
|
|
5349
5349
|
function installedCliVersion() {
|
|
5350
|
-
return true ? "0.0
|
|
5350
|
+
return true ? "0.1.0" : "0.0.0-dev";
|
|
5351
5351
|
}
|
|
5352
5352
|
function compareCliVersions(left, right) {
|
|
5353
5353
|
const parts = (value) => value.split(".").map((part) => Number.parseInt(part, 10) || 0);
|
|
@@ -7163,7 +7163,7 @@ function loadRepoConfig(repoPath, read2 = (file) => import_node_fs18.default.rea
|
|
|
7163
7163
|
const config = parseConfig(parsed ?? {}, configPath);
|
|
7164
7164
|
const requiredVersion = config.devrouter?.version;
|
|
7165
7165
|
if (requiredVersion && !hasWarnedVersionMismatch) {
|
|
7166
|
-
const cliVersion = true ? "0.0
|
|
7166
|
+
const cliVersion = true ? "0.1.0" : "0.0.0-dev";
|
|
7167
7167
|
if (cliVersion !== "0.0.0-dev" && compareSemver2(requiredVersion, cliVersion) > 0) {
|
|
7168
7168
|
hasWarnedVersionMismatch = true;
|
|
7169
7169
|
process.stderr.write(
|
package/dist/devrouter.js
CHANGED
|
@@ -4275,7 +4275,7 @@ function loadRepoConfig(repoPath, read2 = (file) => import_node_fs7.default.read
|
|
|
4275
4275
|
const config = parseConfig(parsed ?? {}, configPath);
|
|
4276
4276
|
const requiredVersion = config.devrouter?.version;
|
|
4277
4277
|
if (requiredVersion && !hasWarnedVersionMismatch) {
|
|
4278
|
-
const cliVersion = true ? "0.0
|
|
4278
|
+
const cliVersion = true ? "0.1.0" : "0.0.0-dev";
|
|
4279
4279
|
if (cliVersion !== "0.0.0-dev" && compareSemver(requiredVersion, cliVersion) > 0) {
|
|
4280
4280
|
hasWarnedVersionMismatch = true;
|
|
4281
4281
|
process.stderr.write(
|
|
@@ -6259,7 +6259,7 @@ var init_reliability_contract = __esm({
|
|
|
6259
6259
|
|
|
6260
6260
|
// src/core/reliability-operation-store.ts
|
|
6261
6261
|
function installedCliVersion() {
|
|
6262
|
-
return true ? "0.0
|
|
6262
|
+
return true ? "0.1.0" : "0.0.0-dev";
|
|
6263
6263
|
}
|
|
6264
6264
|
function compareCliVersions(left, right) {
|
|
6265
6265
|
const parts = (value) => value.split(".").map((part) => Number.parseInt(part, 10) || 0);
|
|
@@ -21117,7 +21117,7 @@ Run several worktrees of one repo in parallel without host/route collisions. A *
|
|
|
21117
21117
|
|
|
21118
21118
|
A managed consumer can gate agent tool calls so a command never starts while the
|
|
21119
21119
|
checkout's environment is mid-transition. Wire \`devrouter harness gate\` as a
|
|
21120
|
-
Claude Code
|
|
21120
|
+
\`PreToolUse\` hook. Claude Code reads it from a settings file:
|
|
21121
21121
|
|
|
21122
21122
|
{
|
|
21123
21123
|
"hooks": {
|
|
@@ -21129,7 +21129,26 @@ Claude Code \`PreToolUse\` hook:
|
|
|
21129
21129
|
}
|
|
21130
21130
|
}
|
|
21131
21131
|
|
|
21132
|
-
The
|
|
21132
|
+
The Codex CLI reads the same hook from \`$CODEX_HOME/hooks.json\`, where the shell
|
|
21133
|
+
tool is named \`exec_command\`:
|
|
21134
|
+
|
|
21135
|
+
{
|
|
21136
|
+
"hooks": {
|
|
21137
|
+
"PreToolUse": [
|
|
21138
|
+
{ "matcher": "exec_command|Bash|shell", "hooks": [
|
|
21139
|
+
{ "type": "command", "command": "devrouter harness gate", "timeout": 180 }
|
|
21140
|
+
] }
|
|
21141
|
+
]
|
|
21142
|
+
}
|
|
21143
|
+
}
|
|
21144
|
+
|
|
21145
|
+
The gate identifies the requesting harness from its payload and answers in that
|
|
21146
|
+
harness's accepted shape: a refusal is a \`deny\` in both, while an allowed call is
|
|
21147
|
+
\`permissionDecision: allow\` for Claude Code and Codex receives a bare completion
|
|
21148
|
+
carrying the same guidance as \`additionalContext\`, because Codex rejects an
|
|
21149
|
+
\`allow\` decision as unsupported hook output.
|
|
21150
|
+
|
|
21151
|
+
|
|
21133
21152
|
the checkout's durable phase is \`queued\`, \`starting\`, \`verifying\`, \`recovering\`
|
|
21134
21153
|
or \`stopping\`, and prints the harness permission decision. The deferral consumes
|
|
21135
21154
|
no model turns because the harness is blocked on the hook. \`--wait-budget-ms\`
|
|
@@ -21141,7 +21160,7 @@ the wait budget: an overrunning hook is not honored, and the tool proceeds under
|
|
|
21141
21160
|
the harness's normal permission rules. Use \`--json\` for the devrouter decision
|
|
21142
21161
|
envelope with wait metrics.
|
|
21143
21162
|
|
|
21144
|
-
|
|
21163
|
+
Gated calls are keyed by the harness \`tool_use_id\` and recorded durably. If the
|
|
21145
21164
|
harness re-delivers a call it already granted or cancelled, the gate returns one
|
|
21146
21165
|
refusal naming the earlier decision instead of waiting again, because that call
|
|
21147
21166
|
may already have run; the agent should verify it and issue a new call. Entries
|
|
@@ -21354,7 +21373,7 @@ function buildOnboardingPrompt(options = {}) {
|
|
|
21354
21373
|
`- When a workspace is active: hosts auto-namespace (\`web.localhost\` \u2192 \`web.<ws>.localhost\`), \`${WORKSPACE_PLACEHOLDER}\` in \`upstream\` is substituted with the token, and the docker \`router\` key is suffixed per workspace. Managed ensure rejects every HTTP/TCP upstream outside that exact alias namespace before mutation. The runtime config is computed in memory only \u2014 the committed \`.devrouter.yml\` is never rewritten.`,
|
|
21355
21374
|
"- TLS: namespaced hosts (`web.<ws>.localhost`) are not covered by the `*.localhost` wildcard; devrouter auto-extends the mkcert cert SANs for active hosts when TLS is enabled.",
|
|
21356
21375
|
"- Lifecycle: after one-time setup, use `devrouter ensure .` for both primary and linked checkouts; never branch on checkout kind or use live verify as startup. Managed consumer images contain no devrouter package/helper: ensure delivers its matching helper at runtime and invokes an exact captured snapshot of the repository-owned post-start adapter. Keep `.devrouter.yml` as the only consumer-side version pin. Use `devrouter stop .` for a non-destructive pause, `devrouter stop . --delete` only for explicit exact-owner cleanup without removing the checkout, and `devrouter exec . -- <command...>` for container commands. Never substitute raw DevPod/Devsy mutations; they bypass the machine-global ownership lock, which serializes provider mutations in a fair arrival-order queue and lets contenders wait up to thirty minutes with throttled stderr progress before failing with the queue position or holder PID and true durations. `workspace up` creates linked worktrees; destructive worktree removal and GC remain ledger-scoped.",
|
|
21357
|
-
"- Managed agent harnesses can defer tool calls through `devrouter harness gate` wired as a Claude Code `
|
|
21376
|
+
"- Managed agent harnesses can defer tool calls through `devrouter harness gate` wired as a `PreToolUse` hook (Claude Code settings, or `$CODEX_HOME/hooks.json` for the Codex CLI with the `exec_command` shell tool): it waits inside the hook process while the checkout's durable phase is queued, starting, verifying, recovering or stopping (no model turns), allows the tool once the phase settles, and refuses once with recovery guidance when `--wait-budget-ms` (default 30000) runs out. It identifies the requesting harness from its payload and answers in that harness's accepted shape, because Codex rejects an `allow` decision. It also records the gated call by harness tool-use id, so a call re-delivered after a granted or cancelled wait returns one refusal instead of repeating a command that may already have run. Lifecycle commands pass through, unreadable journal evidence is allowed rather than blocking the agent, and the hook timeout must exceed the wait budget.",
|
|
21358
21377
|
"- After a drained interrupted ensure, manual exec can use an exactly proven retained Devsy runtime with the plain local Docker command. Revalidate identity for each tooling command until later preparation reconciles startup; preserve configuration, data and interrupted history. This does not repair absent stop baselines or provide OOM protection.",
|
|
21359
21378
|
"- Busy ensure and exec wait up to thirty minutes for a positively identified active lifecycle worker on the same checkout, with stderr progress. Do not stop healthy work to make room. Commands stay serial; timeout or cancellation before admission leaves prior work untouched. A lifecycle fence change cancels admission, and uncertain execution is never replayed.",
|
|
21360
21379
|
"- Ensure reconciles interrupted or never-dispatched ensure after positive worker drainage. Unknown arbitrary exec never replays; use explicit stop to reconcile that uncertainty. Preserve journals and generated configuration if evidence is unavailable. This does not detect or prevent OOM; bounded automatic recovery runs only when the operator enables the capacity recovery policy.",
|
|
@@ -23174,7 +23193,7 @@ function probeInstalledCliVersion(executable) {
|
|
|
23174
23193
|
}
|
|
23175
23194
|
function buildCliPathCheck(options = {}) {
|
|
23176
23195
|
const id5 = "global.cli-path";
|
|
23177
|
-
const runningVersion = options.runningVersion ?? (true ? "0.0
|
|
23196
|
+
const runningVersion = options.runningVersion ?? (true ? "0.1.0" : "0.0.0-dev");
|
|
23178
23197
|
if (runningVersion === "0.0.0-dev") {
|
|
23179
23198
|
return {
|
|
23180
23199
|
id: id5,
|
|
@@ -24022,7 +24041,7 @@ async function buildDoctorReport(options = {}) {
|
|
|
24022
24041
|
suggestion: `Remedy ${expansionNotice.remedy.code}: set a named default profile other than '${expansionNotice.profile}'${remedyCandidates}.`
|
|
24023
24042
|
});
|
|
24024
24043
|
}
|
|
24025
|
-
const cliVersion = true ? "0.0
|
|
24044
|
+
const cliVersion = true ? "0.1.0" : "0.0.0-dev";
|
|
24026
24045
|
const configVersion = config.devrouter?.version;
|
|
24027
24046
|
const installedKnown = cliVersion !== "0.0.0-dev";
|
|
24028
24047
|
const versionComparison = installedKnown && configVersion ? compareSemver(configVersion, cliVersion) : void 0;
|
|
@@ -25128,8 +25147,13 @@ var init_harness_gate = __esm({
|
|
|
25128
25147
|
// src/commands/harness.ts
|
|
25129
25148
|
var harness_exports = {};
|
|
25130
25149
|
__export(harness_exports, {
|
|
25150
|
+
detectHarnessHook: () => detectHarnessHook,
|
|
25131
25151
|
runHarnessCommand: () => runHarnessCommand
|
|
25132
25152
|
});
|
|
25153
|
+
function detectHarnessHook(payload) {
|
|
25154
|
+
const turnId = payload.turn_id;
|
|
25155
|
+
return typeof turnId === "string" && turnId.length > 0 ? "codex" : "claude";
|
|
25156
|
+
}
|
|
25133
25157
|
function readStdin() {
|
|
25134
25158
|
if (process.stdin.isTTY) {
|
|
25135
25159
|
return Promise.resolve("");
|
|
@@ -25181,11 +25205,28 @@ function permitReason(decision) {
|
|
|
25181
25205
|
return "devrouter: environment settled.";
|
|
25182
25206
|
}
|
|
25183
25207
|
}
|
|
25184
|
-
function hookOutput(decision) {
|
|
25208
|
+
function hookOutput(decision, kind) {
|
|
25209
|
+
if (decision.decision === "refuse") {
|
|
25210
|
+
return JSON.stringify({
|
|
25211
|
+
hookSpecificOutput: {
|
|
25212
|
+
hookEventName: "PreToolUse",
|
|
25213
|
+
permissionDecision: "deny",
|
|
25214
|
+
permissionDecisionReason: permitReason(decision)
|
|
25215
|
+
}
|
|
25216
|
+
});
|
|
25217
|
+
}
|
|
25218
|
+
if (kind === "codex") {
|
|
25219
|
+
return JSON.stringify({
|
|
25220
|
+
hookSpecificOutput: {
|
|
25221
|
+
hookEventName: "PreToolUse",
|
|
25222
|
+
additionalContext: permitReason(decision)
|
|
25223
|
+
}
|
|
25224
|
+
});
|
|
25225
|
+
}
|
|
25185
25226
|
return JSON.stringify({
|
|
25186
25227
|
hookSpecificOutput: {
|
|
25187
25228
|
hookEventName: "PreToolUse",
|
|
25188
|
-
permissionDecision:
|
|
25229
|
+
permissionDecision: "allow",
|
|
25189
25230
|
permissionDecisionReason: permitReason(decision)
|
|
25190
25231
|
}
|
|
25191
25232
|
});
|
|
@@ -25209,6 +25250,7 @@ async function runHarnessCommand(method, options, repoPath, dependencies = {}) {
|
|
|
25209
25250
|
`));
|
|
25210
25251
|
const observe = dependencies.observe ?? readHarnessGateObservation;
|
|
25211
25252
|
const raw = await (dependencies.stdin ?? readStdin)();
|
|
25253
|
+
let kind = "claude";
|
|
25212
25254
|
let payload;
|
|
25213
25255
|
try {
|
|
25214
25256
|
const parsed = JSON.parse(raw);
|
|
@@ -25218,28 +25260,35 @@ async function runHarnessCommand(method, options, repoPath, dependencies = {}) {
|
|
|
25218
25260
|
}
|
|
25219
25261
|
if (!payload) {
|
|
25220
25262
|
const decision2 = gateDecision("allow", "hook-payload-invalid");
|
|
25221
|
-
process.stdout.write(
|
|
25222
|
-
|
|
25223
|
-
`)
|
|
25263
|
+
process.stdout.write(
|
|
25264
|
+
json ? `${JSON.stringify(decision2)}
|
|
25265
|
+
` : `${hookOutput(decision2, kind)}
|
|
25266
|
+
`
|
|
25267
|
+
);
|
|
25224
25268
|
return;
|
|
25225
25269
|
}
|
|
25270
|
+
kind = detectHarnessHook(payload);
|
|
25226
25271
|
const toolName = typeof payload.tool_name === "string" ? payload.tool_name : "";
|
|
25227
25272
|
const toolInput = isRecord7(payload.tool_input) ? payload.tool_input : {};
|
|
25228
25273
|
const command = typeof toolInput.command === "string" ? toolInput.command : "";
|
|
25229
25274
|
if (toolName === "Bash" && DEVROUTER_COMMAND_RE.test(command)) {
|
|
25230
25275
|
const decision2 = gateDecision("allow", "devrouter-command");
|
|
25231
|
-
process.stdout.write(
|
|
25232
|
-
|
|
25233
|
-
`)
|
|
25276
|
+
process.stdout.write(
|
|
25277
|
+
json ? `${JSON.stringify(decision2)}
|
|
25278
|
+
` : `${hookOutput(decision2, kind)}
|
|
25279
|
+
`
|
|
25280
|
+
);
|
|
25234
25281
|
return;
|
|
25235
25282
|
}
|
|
25236
25283
|
const sessionCwd = repoPath ?? (typeof payload.cwd === "string" && payload.cwd.length > 0 ? payload.cwd : void 0);
|
|
25237
25284
|
const repoRoot = sessionCwd ? findHarnessGateRepoRoot(sessionCwd) : void 0;
|
|
25238
25285
|
if (!repoRoot) {
|
|
25239
25286
|
const decision2 = gateDecision("allow", "unmanaged-checkout");
|
|
25240
|
-
process.stdout.write(
|
|
25241
|
-
|
|
25242
|
-
`)
|
|
25287
|
+
process.stdout.write(
|
|
25288
|
+
json ? `${JSON.stringify(decision2)}
|
|
25289
|
+
` : `${hookOutput(decision2, kind)}
|
|
25290
|
+
`
|
|
25291
|
+
);
|
|
25243
25292
|
return;
|
|
25244
25293
|
}
|
|
25245
25294
|
const budgetMs = parseHarnessGateBudget(options.waitBudgetMs);
|
|
@@ -25290,7 +25339,7 @@ async function runHarnessCommand(method, options, repoPath, dependencies = {}) {
|
|
|
25290
25339
|
};
|
|
25291
25340
|
process.stdout.write(
|
|
25292
25341
|
json ? `${JSON.stringify({ ...decision2, checkout: repoRoot })}
|
|
25293
|
-
` : `${hookOutput(decision2)}
|
|
25342
|
+
` : `${hookOutput(decision2, kind)}
|
|
25294
25343
|
`
|
|
25295
25344
|
);
|
|
25296
25345
|
return;
|
|
@@ -25348,7 +25397,7 @@ async function runHarnessCommand(method, options, repoPath, dependencies = {}) {
|
|
|
25348
25397
|
}
|
|
25349
25398
|
process.stdout.write(
|
|
25350
25399
|
json ? `${JSON.stringify({ ...decision, checkout: repoRoot })}
|
|
25351
|
-
` : `${hookOutput(decision)}
|
|
25400
|
+
` : `${hookOutput(decision, kind)}
|
|
25352
25401
|
`
|
|
25353
25402
|
);
|
|
25354
25403
|
}
|
|
@@ -29972,7 +30021,7 @@ var init_version = __esm({
|
|
|
29972
30021
|
|
|
29973
30022
|
// src/cli.ts
|
|
29974
30023
|
var import_commander = require("commander");
|
|
29975
|
-
var CLI_VERSION = true ? "0.0
|
|
30024
|
+
var CLI_VERSION = true ? "0.1.0" : "0.0.0-dev";
|
|
29976
30025
|
var VERSION_FLAGS = /* @__PURE__ */ new Set(["-V", "--version"]);
|
|
29977
30026
|
function withErrorHandling(action2) {
|
|
29978
30027
|
return async (...args) => {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@devrouter/cli",
|
|
3
|
-
"version": "0.0
|
|
3
|
+
"version": "0.1.0",
|
|
4
4
|
"description": "Local dev routing CLI with shared Traefik reverse proxy",
|
|
5
5
|
"author": "Roland Schlaefli",
|
|
6
6
|
"homepage": "https://github.com/rschlaefli/devrouter#readme",
|
|
@@ -52,6 +52,8 @@
|
|
|
52
52
|
"qualify:controller": "node --import tsx scripts/qualify-controller.ts",
|
|
53
53
|
"qualify:stop-drift": "node --import tsx scripts/qualify-config-drift-stop.ts",
|
|
54
54
|
"qualify:harness-journey": "./scripts/qualify-harness-journey.sh",
|
|
55
|
+
"qualify:codex-journey": "DR_JOURNEY_HARNESS=codex ./scripts/qualify-harness-journey.sh",
|
|
56
|
+
"qualify:non-node": "./scripts/qualify-non-node-consumer.sh",
|
|
55
57
|
"test:process": "bash scripts/test-devrouter-process.sh",
|
|
56
58
|
"check": "biome check .",
|
|
57
59
|
"check:fix": "biome check --write .",
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
# Upgrade to devrouter 0.1.0
|
|
2
|
+
|
|
3
|
+
This release is the first stable cut of the reliability program. The managed
|
|
4
|
+
lifecycle is now driven by durable intent instead of a live CLI process, capacity
|
|
5
|
+
admission refuses instead of guessing when its evidence is incomplete, and the
|
|
6
|
+
agent harness gate serves Claude Code and the Codex CLI in the shape each of them
|
|
7
|
+
accepts.
|
|
8
|
+
|
|
9
|
+
## What changed for a consumer
|
|
10
|
+
|
|
11
|
+
- A managed `stop` whose physical cessation is proven against a positively
|
|
12
|
+
absent capacity ledger now completes instead of staying in `stopping`
|
|
13
|
+
forever. An intact ledger keeps today's settlement and revision fencing, and
|
|
14
|
+
every other ledger classification still refuses.
|
|
15
|
+
- `devrouter capacity reconcile --yes [--json]` is the explicit forward
|
|
16
|
+
recovery for a provably absent ledger. It refuses without `--yes`, while any
|
|
17
|
+
journal binding remains, and when a declared runtime domain cannot be observed.
|
|
18
|
+
- `devrouter doctor` reports `global.cli-path` when another `devrouter` on
|
|
19
|
+
PATH is newer than the running CLI or when the shell resolves a different
|
|
20
|
+
install. Keep one install authoritative before trusting lifecycle state.
|
|
21
|
+
- `devrouter harness gate` defers one agent tool call while the checkout's
|
|
22
|
+
durable phase is `queued`, `starting`, `verifying`, `recovering` or
|
|
23
|
+
`stopping`, waits inside the hook process with no model turns, and refuses
|
|
24
|
+
once with recovery guidance when the budget runs out. It identifies the
|
|
25
|
+
requesting harness from its payload: Claude Code receives
|
|
26
|
+
`permissionDecision: allow`, the Codex CLI receives the same guidance as
|
|
27
|
+
`additionalContext` because it reports an `allow` decision as unsupported
|
|
28
|
+
hook output. A re-delivered call returns one refusal that names the earlier
|
|
29
|
+
decision instead of repeating a command that may already have run.
|
|
30
|
+
- A fixed host-port conflict whose holder is the shared `devrouter-traefik`
|
|
31
|
+
router names the consumer's own published binding as the side to change instead
|
|
32
|
+
of telling the agent to stop the router.
|
|
33
|
+
- Managed `ensure` accepts a devnet upstream served by any Compose project owned
|
|
34
|
+
by the exact worktree, while a container owned by a different worktree or by a
|
|
35
|
+
shared project outside the checkout is still refused.
|
|
36
|
+
|
|
37
|
+
## Task
|
|
38
|
+
|
|
39
|
+
Use the published 0.1.0 CLI. Update the repository `devrouter.version` metadata
|
|
40
|
+
only when consumer source changes are authorized, and refresh generated agent
|
|
41
|
+
guidance only with its owner's authority. No schema, command, flag, environment
|
|
42
|
+
or journal migration is needed, and the upgrade rewrites no runtime state.
|
|
43
|
+
|
|
44
|
+
Two operating notes matter for scripts and agents:
|
|
45
|
+
|
|
46
|
+
- `ensure` and `stop` write bounded progress lines to stderr. Parse JSON from
|
|
47
|
+
stdout only; do not merge the streams before parsing.
|
|
48
|
+
- Capacity admission, parking consent and automatic recovery stay inert unless an
|
|
49
|
+
operator enables them. Nothing in this release starts, stops or parks a
|
|
50
|
+
consumer environment on its own.
|
|
51
|
+
|
|
52
|
+
## Validation
|
|
53
|
+
|
|
54
|
+
Verify the resolved executable and installed version, then run `devrouter
|
|
55
|
+
doctor` and confirm `global.cli-path` reports one authoritative install and no
|
|
56
|
+
newer shadow copy. For an authorized managed checkout, run `devrouter status`
|
|
57
|
+
and confirm the reported intent, phase, capacity state and attention reason agree
|
|
58
|
+
with the runtime you observe. Require each lifecycle operation's own terminal
|
|
59
|
+
evidence: a status block or a progress line is not stop or ensure success. When
|
|
60
|
+
the gate is wired, prove it once in the harness you use by gating a harmless
|
|
61
|
+
command against a settled checkout.
|
|
62
|
+
|
|
63
|
+
## Report
|
|
64
|
+
|
|
65
|
+
Report the release revision, the executable and installed version, the
|
|
66
|
+
CLI-path diagnostics, the status block for any affected checkout, and the
|
|
67
|
+
terminal result of any authorized lifecycle operation. Keep source and package
|
|
68
|
+
validation separate from consumer readiness or recovery, and record unknown
|
|
69
|
+
ownership or surviving resources as unresolved rather than as recovered.
|