@awak-app/simy-cli 0.7.0 → 0.7.1

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 CHANGED
@@ -1042,6 +1042,12 @@ remaining task findings. `runtime legacy-hooks` routes only the exact shipped
1042
1042
  customized hooks and trust settings remain unchanged. `runtime workflows` refreshes
1043
1043
  existing provider workflows without enabling them.
1044
1044
 
1045
+ `runtime doctor` also returns `taskReadiness` for every existing task: its original
1046
+ ID/state, configuration compatibility or pending migration, and the reason work is
1047
+ queued or waiting. `current` describes configuration only; it does not certify a
1048
+ worker execution, successful repair, or acceptance. User stops and OFF remain
1049
+ visible rather than being counted as migration failures.
1050
+
1045
1051
  For existing opted-in Codex tasks, the controller verifies original session, cwd,
1046
1052
  model, provider link and consent epoch before an idempotent policy migration.
1047
1053
  Frozen goal, criteria, grants, budgets and failure history are retained. Observable
@@ -1058,6 +1064,13 @@ recorded; an audit receipt does not complete a task. SQM evidence stays bound to
1058
1064
  repository/commit/diff scope and does not prove the whole user outcome. Git output
1059
1065
  no longer uses a 10 MiB `execFile` buffer; the full diff is still retained in memory.
1060
1066
 
1067
+ If a later audit finds unmet work while an earlier review was waiting only for
1068
+ human acceptance, the controller reopens independent inspection of the durable
1069
+ findings. Confirmed gaps return to diagnosis, authorized repair and reinspection;
1070
+ incorrect findings need captured counterevidence to be refuted. Restarting the host
1071
+ retains this handoff, task identity and budgets. Actual human acceptance is never
1072
+ supplied by the repair loop, and unresolved native permissions are preserved.
1073
+
1061
1074
  Codex cold recovery and independent audit are supported. Claude retains its
1062
1075
  original live-hook boundary; cold recovery and detached independent audit are not
1063
1076
  implemented in this release and are exposed as a capability gap. Neither an
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@awak-app/simy-cli",
3
- "version": "0.7.0",
3
+ "version": "0.7.1",
4
4
  "description": "Local SIMY executor for non-coding Local Tasks and Agentic Loop runs.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -13,7 +13,7 @@ import {
13
13
  } from "./durable-local-task-steps-contract.js";
14
14
  import { buildExecutionCapabilityContract } from "./execution-capability-contract.js";
15
15
 
16
- export const CLI_VERSION = "0.7.0";
16
+ export const CLI_VERSION = "0.7.1";
17
17
  export const CLI_API_CONTRACT_VERSION = 10;
18
18
 
19
19
  export function narrativeImageCapabilityMetadata() {
@@ -1,6 +1,6 @@
1
1
  import { diagnoseDelivery } from './diagnosis.js';
2
2
  import { linkSessionParent } from '../session-supervision/session-lineage.js';
3
- import { adoptAutonomousPolicy } from '../session-supervision/autonomous-upgrade.js';
3
+ import { adoptAutonomousPolicy, autonomousTaskReadiness } from '../session-supervision/autonomous-upgrade.js';
4
4
  import { deliveryEvidence } from './evidence.js';
5
5
  import { readFile } from 'node:fs/promises';
6
6
  import { startDelivery, checkpointDelivery, controlDelivery, listRecords, rootDirectory, verifyDelivery, readRecord } from '../codex-plugins/simy/hooks/delivery-loop.mjs';
@@ -80,7 +80,8 @@ export async function runDeliveryCommand(argv, { root = rootDirectory(), cwd = p
80
80
  if (args[0] === 'doctor') {
81
81
  const [runtime, host, monitor, records] = await Promise.all([localRuntimeStatus({ root: dirname(root), allowMissingLock: true }),
82
82
  desktopRuntimeStatus({ root: dirname(root) }), consistencyMonitorStatus(root), listRecords(root)]);
83
- output(JSON.stringify({ runtime, host, monitor, tasks: records.flatMap(record => consistencyFindings(record)) }, null, 2)); return 0;
83
+ output(JSON.stringify({ runtime, host, monitor, tasks: records.flatMap(record => consistencyFindings(record)),
84
+ taskReadiness: records.map(record => autonomousTaskReadiness(record)) }, null, 2)); return 0;
84
85
  }
85
86
  const result = args[0] === 'workflows' ? await syncInstalledWorkflows({ installMissing: false })
86
87
  : await (args[0] === 'install' ? installLocalRuntime : localRuntimeStatus)({ root: dirname(root), ...(args[0] === 'status' ? { allowMissingLock: true } : {}) });
@@ -1,5 +1,5 @@
1
1
  import { createHash, randomUUID } from 'node:crypto';
2
- import { automaticContinuationAllowed, listRecords, updateRecord, queuePurposeDiagnosis, queuePurposeVerification } from '../codex-plugins/simy/hooks/delivery-loop.mjs';
2
+ import { automaticContinuationAllowed, listRecords, updateRecord, queuePurposeDiagnosis, queuePurposeVerification, managedBudgetReason } from '../codex-plugins/simy/hooks/delivery-loop.mjs';
3
3
  import { autonomousPolicyAuthorized, policyContractIdentity, acceptanceIdentity, legacyIndependentCriterion, effectiveAcceptanceCriteria } from '../codex-plugins/simy/hooks/acceptance-policy.mjs';
4
4
  import { purposeFailureMode } from '../codex-plugins/simy/hooks/purpose-search.mjs';
5
5
 
@@ -28,6 +28,34 @@ export function policyAdoptionPending(record) {
28
28
  (m.acceptanceRoutes?.[c.id]?.source !== 'explicit_user_runtime_policy' || m.acceptanceRoutes[c.id].authorizationId !== m.policyAuthorization.id || m.acceptanceRoutes[c.id].criterionDigest !== acceptanceIdentity(c))));
29
29
  }
30
30
 
31
+ // Read-only rollout accounting. Configuration compatibility is not evidence of
32
+ // a dispatched worker, successful repair, or accepted user outcome.
33
+ export function autonomousTaskReadiness(record, now = Date.now()) {
34
+ const run = record?.run, m = run?.managed;
35
+ const result = { provider: record.provider, session: record.session, taskId: m?.taskId || null,
36
+ state: run?.state || 'not_started', configuration: 'not_applicable', continuation: 'not_enrolled' };
37
+ if (!m || record.provider !== 'codex') return result;
38
+ const current = m.purpose?.enabled === true && m.purpose.version === 2 && m.purpose.decisionPolicy === 'llm-first-hc102' &&
39
+ m.purpose.failureBudgetMode === purposeFailureMode(record) && m.controllerContractVersion === AUTONOMOUS_STATE_CONTRACT &&
40
+ m.resumeChannel === 'codex_app_server' && !policyAdoptionPending(record);
41
+ result.configuration = current ? 'current' : 'migration_pending';
42
+ if (record.providerArchived) result.continuation = 'archived';
43
+ else if (run.state === 'stopped' || run.stopReason?.actor === 'user' || run.state === 'paused' && pauseOrigin(run) === 'user') result.continuation = 'user_stopped';
44
+ else if (!record.autoContinue || !automaticContinuationAllowed(record)) result.continuation = 'off';
45
+ else if (['completed', 'reported_done', 'failed', 'budget_exhausted'].includes(run.state)) result.continuation = run.state;
46
+ else if (!current) result.continuation = 'migration_pending';
47
+ else if (m.usageBlocked || managedBudgetReason(run, now)) result.continuation = 'budget_boundary';
48
+ else if (m.pendingApproval || m.nativeWait || m.purpose.nativeBoundary || m.executionUncertain || m.providerWarning) result.continuation = 'authority_or_execution_boundary';
49
+ else if (['pending', 'transferred', 'pending_received'].includes(m.handoff?.status)) result.continuation = 'handoff_pending';
50
+ else if (m.resumeLease || m.liveWorkspaceLease || m.outcomeAudit?.lease) result.continuation = 'owned_attempt';
51
+ else if (run.state === 'verification_pending') {
52
+ const findings = m.purpose.evidence?.findings?.filter(row => row.status === 'open' && row.owner !== 'human') || [];
53
+ const pending = effectiveAcceptanceCriteria(m).filter(c => !m.verified?.[c.id]);
54
+ result.continuation = findings.length ? 'outcome_reinspection_pending' : pending.length && pending.every(c => c.check.type === 'manual') ? 'manual_acceptance' : 'verification_pending';
55
+ } else result.continuation = run.state === 'recovery_required' && m.autoRecovery ? 'recovery_queued' : run.state;
56
+ return result;
57
+ }
58
+
31
59
  // Controller-only, idempotent migration. Frozen criteria and grants remain
32
60
  // byte-identical, including each criterion's original verifier.
33
61
  export function upgradeAutonomousRecord(record, { rules, now, metadata } = {}) {
@@ -2,7 +2,7 @@ import { preparePurposeCandidate } from './purpose-candidate.js';
2
2
  import { requireProviderLink } from '../provider-links.js';
3
3
  import { searchEnabled, purposeFailureMode } from '../codex-plugins/simy/hooks/purpose-search.mjs';
4
4
  import { createHash } from 'node:crypto';
5
- import { listRecords, readRecord, updateRecord, automaticContinuationAllowed, queuePurposeDiagnosis, queuePurposeVerification } from '../codex-plugins/simy/hooks/delivery-loop.mjs';
5
+ import { listRecords, readRecord, updateRecord, automaticContinuationAllowed, queuePurposeDiagnosis, queuePurposeVerification, managedBudgetReason } from '../codex-plugins/simy/hooks/delivery-loop.mjs';
6
6
  import { openCodexAppServer, readCodexSession, observeCodexAuthentication } from './codex-app-server.js';
7
7
  import { realpath } from 'node:fs/promises';
8
8
  import { loadPurposeRuleSet } from './purpose-instruction.js';
@@ -10,6 +10,16 @@ import { purposeEnabled } from '../codex-plugins/simy/hooks/purpose-cycle.mjs';
10
10
  import { upgradeAutonomousRecord, upgradeEligible, policyAdoptionPending } from './autonomous-upgrade.js';
11
11
  import { effectiveAcceptanceCriteria } from '../codex-plugins/simy/hooks/acceptance-policy.mjs';
12
12
 
13
+ function auditReinspectionEligible(run, now) {
14
+ const m = run?.managed;
15
+ return run?.state === 'verification_pending' && searchEnabled(m) &&
16
+ m.purpose.evidence?.findings?.some(row => row.status === 'open' && row.owner !== 'human') &&
17
+ !m.resumeLease && !m.liveWorkspaceLease && !m.outcomeAudit?.lease &&
18
+ !m.pendingApproval && !m.nativeWait && !m.purpose.nativeBoundary && !m.executionUncertain &&
19
+ !m.providerWarning && !m.usageBlocked && !managedBudgetReason(run, now) &&
20
+ !['pending', 'transferred', 'pending_received'].includes(m.handoff?.status);
21
+ }
22
+
13
23
  export async function observePurposeTurn(record, { openClient = openCodexAppServer } = {}) {
14
24
  const client = await openClient();
15
25
  try {
@@ -41,6 +51,7 @@ export async function observeUpgradeMetadata(record, { openClient = openCodexApp
41
51
  }
42
52
 
43
53
  export async function reconcilePurposeTasks({ root, signal, prepareCandidate = preparePurposeCandidate, now = Date.now(), observeTurn = observePurposeTurn, observeAuthentication = observeCodexAuthentication, readRecords = () => listRecords(root), observeMetadata = observeUpgradeMetadata }) {
54
+ const started = Date.now();
44
55
  let rules;
45
56
  const deferred = [];
46
57
 
@@ -234,6 +245,24 @@ export async function reconcilePurposeTasks({ root, signal, prepareCandidate = p
234
245
  obstacle ||= { type: run.blocker, detail: run.managed.understandingQuestion?.text || run.managed.userQuestion ||
235
246
  run.managed.dependencyWait?.condition || run.managed.pendingApproval?.summary || run.reason };
236
247
  } else if (run.state === 'verification_pending') {
248
+ // A later independent audit can invalidate an earlier "human only"
249
+ // disposition. Reinspect its durable findings through the canonical
250
+ // verifier before assigning repairs; a report is neither a pass nor an
251
+ // authorization to replay an effect. This also recovers the handoff if
252
+ // the host exited after persisting findings but before scheduling work.
253
+ if (auditReinspectionEligible(run, now)) {
254
+ await persist(current => {
255
+ const inspectionNow = now + Math.max(0, Date.now() - started);
256
+ // Auditing can change findings, authority and usage without advancing
257
+ // the checkpoint CAS. Recheck the full predicate under the state lock.
258
+ if (current?.run?.id !== run.id || current.run.revision !== run.revision || current.run.state !== run.state || current.run.updatedAt !== run.updatedAt ||
259
+ !auditReinspectionEligible(current.run, inspectionNow)) return current;
260
+ queuePurposeVerification(current.run, 'Independent audit left unresolved outcome findings. Reinspect original requirements and current primary evidence; repair genuine gaps or refute incorrect findings with bound counterevidence. Preserve explicit human acceptance.', inspectionNow);
261
+ current.run.managed.nextAttemptAt = inspectionNow;
262
+ return current;
263
+ });
264
+ continue;
265
+ }
237
266
  if (searchEnabled(run.managed) && (run.managed.purpose.search?.review?.passed || run.managed.purpose.search?.review?.needsHuman) && effectiveAcceptanceCriteria(run.managed).filter(c => !run.managed.verified[c.id]).every(c => c.check.type === 'manual')) continue;
238
267
  obstacle = { type: 'verification', detail: run.managed.acceptanceCriteria.filter(c => !run.managed.verified[c.id]).map(c => c.id).join(',') };
239
268
  } else if (run.state === 'recovery_required' && ['provider_error', 'process_exit', 'shutdown_cancelled', 'interrupted_owner'].includes(run.recoveryCause) &&