@zq-silk/yui 0.6.0 → 0.6.2
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 +5 -5
- package/dist/agent/managedRuntimeEnvironment.js +2 -1
- package/dist/cli/commandCatalog.js +251 -13
- package/dist/cli/updateOrchestrator.js +8 -0
- package/dist/cli/updatePorts.js +76 -22
- package/dist/cli.js +264 -20
- package/dist/commands/configCommands.js +83 -9
- package/dist/commands/controllerCommands.js +103 -0
- package/dist/commands/deliveryGuardPreflight.js +30 -0
- package/dist/commands/durableJobCommands.js +231 -0
- package/dist/commands/executionAuditCommands.js +193 -0
- package/dist/commands/grantCommands.js +374 -0
- package/dist/commands/projectCommands.js +119 -81
- package/dist/commands/releaseCommands.js +444 -0
- package/dist/commands/resourcesCommands.js +274 -0
- package/dist/commands/sessionCommands.js +104 -0
- package/dist/commands/taskActor.js +117 -0
- package/dist/commands/taskChangeSetCommands.js +60 -0
- package/dist/commands/taskCommands.js +618 -202
- package/dist/commands/taskCompletionGate.js +78 -1
- package/dist/commands/taskContextCommand.js +33 -6
- package/dist/commands/taskInputCommands.js +1 -1
- package/dist/commands/taskIntegrationCommands.js +136 -33
- package/dist/commands/taskIntegrationQueueCommands.js +228 -0
- package/dist/commands/taskNextActionCommand.js +100 -0
- package/dist/commands/taskOverlapCommands.js +120 -0
- package/dist/commands/taskOverviewCommand.js +36 -8
- package/dist/commands/telemetryCommands.js +330 -0
- package/dist/commands/workflowCommands.js +415 -0
- package/dist/config/yuiConfig.js +62 -0
- package/dist/controller/clientRuntime.js +42 -1
- package/dist/controller/controller.js +402 -56
- package/dist/controller/controllerMain.js +25 -2
- package/dist/controller/domainIdentity.js +16 -8
- package/dist/controller/fileSchedulerStoreAdapter.js +423 -31
- package/dist/controller/handoverCandidate.js +168 -0
- package/dist/controller/jobClient.js +102 -0
- package/dist/controller/jobControl.js +613 -0
- package/dist/controller/jobSupervisor.js +498 -0
- package/dist/controller/providerHookRunFence.js +34 -5
- package/dist/controller/resourceCleanupLinux.js +18 -9
- package/dist/controller/resourceInventoryLinux.js +90 -39
- package/dist/controller/runtime.js +165 -15
- package/dist/controller/runtimeEventInbox.js +234 -57
- package/dist/controller/runtimeEventProcessor.js +297 -58
- package/dist/controller/sessionOwnerReconciliation.js +321 -0
- package/dist/core/controllerServer.js +416 -27
- package/dist/core/controllerTelemetry.js +167 -0
- package/dist/doctor/doctor.js +113 -16
- package/dist/domain/validation.js +9 -0
- package/dist/execution/executionGroup.js +40 -3
- package/dist/executor/agentExecutor.js +6 -3
- package/dist/executor/effectiveLaunch.js +52 -0
- package/dist/executor/executorRegistry.js +50 -0
- package/dist/executor/fileRoleLaunchPlanner.js +61 -6
- package/dist/grant/capabilityGrant.js +282 -0
- package/dist/integration/changeSet.js +16 -3
- package/dist/integration/changeSetManifest.js +46 -0
- package/dist/integration/gitIntegrationService.js +528 -147
- package/dist/integration/integrationAttempt.js +54 -5
- package/dist/integration/integrationQueueEntry.js +221 -0
- package/dist/integration/integrationQueueService.js +955 -0
- package/dist/integration/manifestTags.js +99 -0
- package/dist/integration/overlapDiagnostics.js +211 -0
- package/dist/job/durableJob.js +449 -0
- package/dist/job/jobRunner.js +350 -0
- package/dist/lifecycle/exactRunTerminalization.js +24 -2
- package/dist/lifecycle/providerErrorClass.js +126 -0
- package/dist/message/message.js +16 -3
- package/dist/observability/executionAudit.js +545 -0
- package/dist/observability/faultClassification.js +160 -0
- package/dist/observability/runtimeIdentity.js +367 -0
- package/dist/release/fakeReleasePorts.js +55 -0
- package/dist/release/releaseHandover.js +475 -0
- package/dist/release/releaseIdempotencyStore.js +165 -0
- package/dist/release/releaseWorkflow.js +459 -0
- package/dist/release/releaseWorkflowEngine.js +688 -0
- package/dist/release/releaseWorkflowPorts.js +1720 -0
- package/dist/release/runtimeRelease.js +495 -0
- package/dist/release/workflowFileLock.js +218 -0
- package/dist/repository/gitWorkspace.js +177 -1
- package/dist/repository/projectMaintenanceLock.js +315 -0
- package/dist/repository/taskWorkspaceCoordinator.js +87 -17
- package/dist/repository/taskWorkspacePreparer.js +1091 -517
- package/dist/resources/autoResourceGc.js +116 -0
- package/dist/resources/liveReferences.js +574 -0
- package/dist/resources/resourceDiscovery.js +477 -0
- package/dist/resources/resourceGc.js +645 -0
- package/dist/resources/resourceRegistrar.js +256 -0
- package/dist/resources/resourceRegistry.js +150 -0
- package/dist/resources/resourceRegistryStore.js +41 -0
- package/dist/resources/resourceTypes.js +42 -0
- package/dist/resources/sqliteResourceRegistry.js +111 -0
- package/dist/review/reviewConfig.js +10 -0
- package/dist/review/reviewFinding.js +240 -0
- package/dist/review/reviewFindingLedger.js +545 -0
- package/dist/review/reviewOutcomeClassifier.js +61 -0
- package/dist/review/reviewRound.js +56 -4
- package/dist/run/agentRun.js +80 -4
- package/dist/run/providerRetry.js +84 -0
- package/dist/run/providerRetryConfig.js +63 -0
- package/dist/run/yieldReceipt.js +65 -0
- package/dist/runtime/exactControlPlane.js +79 -2
- package/dist/runtime/index.js +4 -0
- package/dist/runtime/sessionOwnerIdentity.js +269 -0
- package/dist/runtime/sessionOwnerRegistry.js +132 -0
- package/dist/runtime/sessionReconciliation.js +93 -0
- package/dist/runtime/sessionTerminationGuard.js +211 -0
- package/dist/runtime/taskRuntimeIsolation.js +13 -0
- package/dist/runtime/tmuxAdapters.js +34 -1
- package/dist/scheduler/actionability.js +155 -0
- package/dist/scheduler/activeRoleRunDelivery.js +14 -5
- package/dist/scheduler/activeTaskProgress.js +60 -0
- package/dist/scheduler/leaderWakeupProcessor.js +22 -11
- package/dist/scheduler/roleRunStall.js +135 -29
- package/dist/scheduler/taskExecutionProjection.js +11 -0
- package/dist/storage/compatibleTaskStore.js +112 -5
- package/dist/storage/migration/productionRegistry.js +736 -1
- package/dist/storage/sqliteSchema.js +264 -3
- package/dist/storage/sqliteStore.js +487 -13
- package/dist/storage/storeRpc.js +21 -0
- package/dist/storage/taskStore.js +974 -21
- package/dist/storage/upgrade/homeClassification.js +120 -2
- package/dist/storage/upgrade/migrationReceipt.js +67 -0
- package/dist/storage/upgrade/pseudoLayoutRepair.js +241 -0
- package/dist/storage/upgrade/recordVersions.js +10 -1
- package/dist/storage/upgrade/sqliteMigrationTarget.js +58 -6
- package/dist/storage/upgrade/sqliteRecordMigrationTarget.js +290 -0
- package/dist/storage/upgrade/sqliteStateMigration.js +258 -2
- package/dist/storage/upgrade/upgradeOrchestrator.js +482 -16
- package/dist/task/deliveryGuard.js +226 -0
- package/dist/task/nextAction.js +738 -0
- package/dist/task/repairWave.js +137 -0
- package/dist/task/taskRecordReference.js +6 -1
- package/dist/telemetry/sqliteTelemetryStore.js +387 -0
- package/dist/telemetry/telemetryCompaction.js +251 -0
- package/dist/telemetry/telemetryConfig.js +64 -0
- package/dist/telemetry/telemetryRouter.js +32 -0
- package/dist/telemetry/telemetryStore.js +19 -0
- package/dist/telemetry/telemetryWiring.js +33 -0
- package/dist/tmux/tmuxManager.js +20 -1
- package/dist/tmux/tmuxSocketEndpoint.js +20 -0
- package/dist/verification/gateArtifact.js +216 -0
- package/dist/verification/gateArtifactStore.js +87 -0
- package/dist/verification/verificationGateService.js +414 -0
- package/dist/verification/verificationPlan.js +308 -0
- package/dist/workspace/gitChangeSetCapture.js +12 -2
- package/dist/workspace/workItemChangeSetManager.js +60 -3
- package/package.json +1 -1
- package/skills/yui-leader/SKILL.md +8 -0
package/README.md
CHANGED
|
@@ -703,13 +703,13 @@ hiding the resources that remain. Use `--all` to include discovered Yui homes.
|
|
|
703
703
|
|
|
704
704
|
`controller restart` replaces the Controller process and its scheduler/socket services with the currently installed Yui version. It does not stop or restart managed tmux/Agent sessions.
|
|
705
705
|
|
|
706
|
-
Its recovery reconciliation runs every 120 seconds by default. Normal durable state changes enqueue a Task, Role, or Operator key and return immediately; keys received in the same fixed 100 ms window trigger one non-overlapping targeted pass. Operator presentation has an independent lane, so a blocked Task workspace operation cannot delay a user question. Periodic Git/worktree work is limited to Tasks with durable Task-mailbox work, while active Role liveness uses one tmux inventory. A Codex turn-complete Hook writes directly to storage without starting or waiting for the Controller, then gives a legal yield/input/completion two seconds to win before closing a forgotten Run. Durable mailboxes freeze the current batch while new signals merge into the next batch
|
|
706
|
+
Its recovery reconciliation runs every 120 seconds by default. Normal durable state changes enqueue a Task, Role, or Operator key and return immediately; keys received in the same fixed 100 ms window trigger one non-overlapping targeted pass. Operator presentation has an independent lane, so a blocked Task workspace operation cannot delay a user question. Periodic Git/worktree work is limited to Tasks with durable Task-mailbox work, while active Role liveness uses one tmux inventory. A Codex turn-complete Hook writes directly to storage without starting or waiting for the Controller, then gives a legal yield/input/completion two seconds to win before closing a forgotten Run. Durable mailboxes freeze the current batch while new signals merge into the next batch. Task-orchestration failures retain the exact Controller-owned processing batch for two bounded fast retries and later periodic recovery; a successful retry completes that batch before newer pending work is claimed. Recommended InputRequest and pending Turn deadlines share one nearest-deadline selector and therefore do not wait for the recovery interval. Explicit `task reconcile` still requests an immediate recovery pass. The retained loop is:
|
|
707
707
|
|
|
708
|
-
1.
|
|
709
|
-
2.
|
|
708
|
+
1. dispatch pending Leader wakes whose Task workspaces are already ready;
|
|
709
|
+
2. prepare active Project Task main worktrees with durable orchestration work;
|
|
710
710
|
3. deliver queued Worker Runs;
|
|
711
|
-
4.
|
|
712
|
-
5. dispatch
|
|
711
|
+
4. resolve due Turn completions and reconcile Role liveness;
|
|
712
|
+
5. dispatch Leader work created or unblocked by the later recovery phases.
|
|
713
713
|
|
|
714
714
|
Automated input is sent only through tmux. Each pass performs one non-blocking process-state readiness check; a busy startup is retried through a small bounded mailbox timer, while later busy sessions are normally woken by Codex turn-complete events. A pane-local receipt prevents the same Run from being typed twice after a Controller retry.
|
|
715
715
|
|
|
@@ -219,6 +219,12 @@ const taskChildren = [
|
|
|
219
219
|
summary: "Show consolidated working context for a Task.",
|
|
220
220
|
usage: "yui task context <task>"
|
|
221
221
|
},
|
|
222
|
+
{
|
|
223
|
+
name: "next-action",
|
|
224
|
+
summary: "Project the durable Task records into one protocol-level next action.",
|
|
225
|
+
usage: "yui task next-action <task> [--json]",
|
|
226
|
+
options: ["--json"]
|
|
227
|
+
},
|
|
222
228
|
{
|
|
223
229
|
name: "archive",
|
|
224
230
|
summary: "Archive a Task after confirming the main worktree outcome.",
|
|
@@ -308,6 +314,75 @@ const taskChildren = [
|
|
|
308
314
|
}
|
|
309
315
|
]
|
|
310
316
|
},
|
|
317
|
+
{
|
|
318
|
+
name: "grant",
|
|
319
|
+
summary: "Manage capability grants for a Task. Issue and revoke require the authenticated global Operator session.",
|
|
320
|
+
sections: [{ id: "manage", title: "Commands", entries: ["issue", "show", "list", "revoke"] }],
|
|
321
|
+
children: [
|
|
322
|
+
{
|
|
323
|
+
name: "issue",
|
|
324
|
+
summary: "Issue a capability grant to a Task. Requires the authenticated global Operator session; the granter is bound to it.",
|
|
325
|
+
usage: "yui task grant issue <task> --action <name> (repeatable) [--scope-project <id>...] [--scope-repo <owner/name>...] [--scope-package <name>...] [--scope-home <path>] [--param <name=v1,v2>...] [--expires-at <iso-8601>] [--max-uses <int>] [--irreversibility-ceiling <none|reversible|irreversible>]",
|
|
326
|
+
options: ["--action", "--scope-project", "--scope-repo", "--scope-package", "--scope-home", "--param", "--expires-at", "--max-uses", "--irreversibility-ceiling"]
|
|
327
|
+
},
|
|
328
|
+
{
|
|
329
|
+
name: "show",
|
|
330
|
+
summary: "Show one capability grant.",
|
|
331
|
+
usage: "yui task grant show <task> <grant-id>"
|
|
332
|
+
},
|
|
333
|
+
{
|
|
334
|
+
name: "list",
|
|
335
|
+
summary: "List capability grants for a Task.",
|
|
336
|
+
usage: "yui task grant list <task>"
|
|
337
|
+
},
|
|
338
|
+
{
|
|
339
|
+
name: "revoke",
|
|
340
|
+
summary: "Revoke a capability grant. Requires the authenticated global Operator session; the revoker is bound to it.",
|
|
341
|
+
usage: "yui task grant revoke <task> <grant-id>",
|
|
342
|
+
options: []
|
|
343
|
+
}
|
|
344
|
+
]
|
|
345
|
+
},
|
|
346
|
+
{
|
|
347
|
+
name: "workflow",
|
|
348
|
+
summary: "Manage release workflows for a Task.",
|
|
349
|
+
sections: [{ id: "manage", title: "Commands", entries: ["create", "show", "list", "run", "resume", "status"] }],
|
|
350
|
+
children: [
|
|
351
|
+
{
|
|
352
|
+
name: "create",
|
|
353
|
+
summary: "Create a release workflow for a Task.",
|
|
354
|
+
usage: "yui task workflow create <task> --grant <grant-id> --source-repo <owner/name> --source-commit <sha> [--source-artifact <name@integrity>] --step <id>:<kind> (repeatable) [--step-irreversibility <id>=<level> (repeatable)] [--step-param <id>:<key>=<value> (repeatable)]",
|
|
355
|
+
options: ["--grant", "--source-repo", "--source-commit", "--source-artifact", "--step", "--step-irreversibility", "--step-param"]
|
|
356
|
+
},
|
|
357
|
+
{
|
|
358
|
+
name: "show",
|
|
359
|
+
summary: "Show one release workflow.",
|
|
360
|
+
usage: "yui task workflow show <task> <workflow-id>"
|
|
361
|
+
},
|
|
362
|
+
{
|
|
363
|
+
name: "list",
|
|
364
|
+
summary: "List release workflows for a Task.",
|
|
365
|
+
usage: "yui task workflow list <task>"
|
|
366
|
+
},
|
|
367
|
+
{
|
|
368
|
+
name: "run",
|
|
369
|
+
summary: "Run a release workflow from its resume cursor.",
|
|
370
|
+
usage: "yui task workflow run <task> <workflow-id> [--grant <grant-id>] [--max-steps <int>]",
|
|
371
|
+
options: ["--grant", "--max-steps"]
|
|
372
|
+
},
|
|
373
|
+
{
|
|
374
|
+
name: "resume",
|
|
375
|
+
summary: "Resume a release workflow from its first unconfirmed step.",
|
|
376
|
+
usage: "yui task workflow resume <task> <workflow-id> [--grant <grant-id>] [--max-steps <int>]",
|
|
377
|
+
options: ["--grant", "--max-steps"]
|
|
378
|
+
},
|
|
379
|
+
{
|
|
380
|
+
name: "status",
|
|
381
|
+
summary: "Show a release workflow and its step states.",
|
|
382
|
+
usage: "yui task workflow status <task> <workflow-id>"
|
|
383
|
+
}
|
|
384
|
+
]
|
|
385
|
+
},
|
|
311
386
|
{
|
|
312
387
|
name: "role",
|
|
313
388
|
summary: "Manage Roles within a Task.",
|
|
@@ -508,7 +583,7 @@ const taskChildren = [
|
|
|
508
583
|
{
|
|
509
584
|
name: "review",
|
|
510
585
|
summary: "Control Task-final ReviewRounds.",
|
|
511
|
-
sections: [{ id: "manage", title: "Commands", entries: ["request", "group", "retry"] }],
|
|
586
|
+
sections: [{ id: "manage", title: "Commands", entries: ["request", "group", "retry", "finding"] }],
|
|
512
587
|
children: [
|
|
513
588
|
{
|
|
514
589
|
name: "request",
|
|
@@ -533,13 +608,46 @@ const taskChildren = [
|
|
|
533
608
|
name: "retry",
|
|
534
609
|
summary: "Retry a failed Task-final ReviewRound without a Reviewer Run.",
|
|
535
610
|
usage: "yui task review retry <task>/<review-round>"
|
|
611
|
+
},
|
|
612
|
+
{
|
|
613
|
+
name: "finding",
|
|
614
|
+
summary: "Inspect and disposition the cross-Round Review finding ledger.",
|
|
615
|
+
executable: true,
|
|
616
|
+
sections: [{ id: "manage", title: "Commands", entries: ["list", "dispose", "repair-wave", "extract"] }],
|
|
617
|
+
children: [
|
|
618
|
+
{
|
|
619
|
+
name: "list",
|
|
620
|
+
summary: "List the Task's review findings with disposition and repair lineage.",
|
|
621
|
+
usage: "yui task review finding list <task>"
|
|
622
|
+
},
|
|
623
|
+
{
|
|
624
|
+
name: "dispose",
|
|
625
|
+
summary: "Record one Leader disposition for a review finding.",
|
|
626
|
+
usage: "yui task review finding dispose <task>/<finding> --disposition <fixed-pending-review|verified-fixed|accepted-risk|not-actionable|superseded> [--work-item <id>] [--commit <sha>] [--verification <text>] [--note <text>] [--superseded-by <stable-key>]",
|
|
627
|
+
options: ["--disposition", "--work-item", "--commit", "--verification", "--note", "--superseded-by"],
|
|
628
|
+
optionValues: {
|
|
629
|
+
"--disposition": ["fixed-pending-review", "verified-fixed", "accepted-risk", "not-actionable", "superseded"]
|
|
630
|
+
}
|
|
631
|
+
},
|
|
632
|
+
{
|
|
633
|
+
name: "repair-wave",
|
|
634
|
+
summary: "Group open P1/P2 findings into parallel repair groups by overlap.",
|
|
635
|
+
usage: "yui task review finding repair-wave <task> [--create]",
|
|
636
|
+
options: ["--create"]
|
|
637
|
+
},
|
|
638
|
+
{
|
|
639
|
+
name: "extract",
|
|
640
|
+
summary: "Reconcile findings from one completed ReviewRound into the ledger.",
|
|
641
|
+
usage: "yui task review finding extract <task>/<review-round>"
|
|
642
|
+
}
|
|
643
|
+
]
|
|
536
644
|
}
|
|
537
645
|
]
|
|
538
646
|
},
|
|
539
647
|
{
|
|
540
648
|
name: "integration",
|
|
541
649
|
summary: "Safely integrate ChangeSets with Leader-owned conflict decisions.",
|
|
542
|
-
sections: [{ id: "manage", title: "Commands", entries: ["start", "continue", "resolve", "abort", "list", "show", "cleanup"] }],
|
|
650
|
+
sections: [{ id: "manage", title: "Commands", entries: ["start", "continue", "resolve", "abort", "supersede", "list", "show", "cleanup", "queue"] }],
|
|
543
651
|
children: [
|
|
544
652
|
{
|
|
545
653
|
name: "start",
|
|
@@ -565,9 +673,29 @@ const taskChildren = [
|
|
|
565
673
|
usage: "yui task integration abort <task>/<integration> --reason <text>",
|
|
566
674
|
options: ["--reason"]
|
|
567
675
|
},
|
|
676
|
+
{
|
|
677
|
+
name: "supersede",
|
|
678
|
+
summary: "Mark a committed Integration as obsolete, retaining its evidence.",
|
|
679
|
+
usage: "yui task integration supersede <task>/<integration> --reason <text>",
|
|
680
|
+
options: ["--reason"]
|
|
681
|
+
},
|
|
568
682
|
{ name: "list", summary: "List Integration Attempts.", usage: "yui task integration list <task>" },
|
|
569
683
|
{ name: "show", summary: "Show one Integration Attempt.", usage: "yui task integration show <task>/<integration>" },
|
|
570
|
-
{ name: "cleanup", summary: "Remove a terminal Integration worktree and branch.", usage: "yui task integration cleanup <task>/<integration>" }
|
|
684
|
+
{ name: "cleanup", summary: "Remove a terminal Integration worktree and branch.", usage: "yui task integration cleanup <task>/<integration>" },
|
|
685
|
+
{
|
|
686
|
+
name: "queue",
|
|
687
|
+
summary: "Manage the serialized integration queue.",
|
|
688
|
+
sections: [{ id: "queue", title: "Commands", entries: ["enqueue", "list", "show", "process", "supersede", "requeue", "reconcile"] }],
|
|
689
|
+
children: [
|
|
690
|
+
{ name: "enqueue", summary: "Enqueue a ChangeSet for serialized integration.", usage: "yui task integration queue enqueue <task> --project <project> --change-set <id> [--target <ref>] [--check <command> ...]", options: ["--project", "--change-set", "--target", "--check"] },
|
|
691
|
+
{ name: "list", summary: "List integration queue entries.", usage: "yui task integration queue list <task> [--project <project>]", options: ["--project"] },
|
|
692
|
+
{ name: "show", summary: "Show one integration queue entry.", usage: "yui task integration queue show <task>/<entry>" },
|
|
693
|
+
{ name: "process", summary: "Process queued integration entries.", usage: "yui task integration queue process <task> [--project <project>] [--limit <n>]", options: ["--project", "--limit"] },
|
|
694
|
+
{ name: "supersede", summary: "Supersede a queued entry.", usage: "yui task integration queue supersede <task>/<entry> --reason <text>", options: ["--reason"] },
|
|
695
|
+
{ name: "requeue", summary: "Requeue a conflicted entry.", usage: "yui task integration queue requeue <task>/<entry>" },
|
|
696
|
+
{ name: "reconcile", summary: "Reconcile a blocked entry.", usage: "yui task integration queue reconcile <task>/<entry>" }
|
|
697
|
+
]
|
|
698
|
+
}
|
|
571
699
|
]
|
|
572
700
|
},
|
|
573
701
|
{
|
|
@@ -635,6 +763,20 @@ const taskChildren = [
|
|
|
635
763
|
{ name: "show", summary: "Show one Task event.", usage: "yui task event show <task> <event>" }
|
|
636
764
|
]
|
|
637
765
|
},
|
|
766
|
+
{
|
|
767
|
+
name: "overlap",
|
|
768
|
+
summary: "Show read-only cross-Task overlap diagnostics.",
|
|
769
|
+
usage: "yui task overlap [--project <project>] [--base <sha>] [--task <task> ...]",
|
|
770
|
+
options: ["--project", "--base", "--task"]
|
|
771
|
+
},
|
|
772
|
+
{
|
|
773
|
+
name: "change-set",
|
|
774
|
+
summary: "Inspect ChangeSets captured from WorkItem Candidates.",
|
|
775
|
+
sections: [{ id: "inspect", title: "Commands", entries: ["show"] }],
|
|
776
|
+
children: [
|
|
777
|
+
{ name: "show", summary: "Show one ChangeSet.", usage: "yui task change-set show <task>/<change-set>" }
|
|
778
|
+
]
|
|
779
|
+
},
|
|
638
780
|
{ name: "enter", summary: "Enter a Task Role, defaulting to Leader.", usage: "yui task enter <task> [role]" }
|
|
639
781
|
];
|
|
640
782
|
export const ROOT_COMMAND = buildNode({
|
|
@@ -647,7 +789,8 @@ export const ROOT_COMMAND = buildNode({
|
|
|
647
789
|
] },
|
|
648
790
|
{ id: "workflow", title: "Workflow", entries: ["operator", "project", "task"] },
|
|
649
791
|
{ id: "configuration", title: "Configuration", entries: ["config", "agent", "profile", "role"] },
|
|
650
|
-
{ id: "operations", title: "Operations", entries: ["web", "controller", "jobs"] },
|
|
792
|
+
{ id: "operations", title: "Operations", entries: ["web", "controller", "execution", "job", "jobs", "telemetry", "release"] },
|
|
793
|
+
{ id: "resources", title: "Resources", entries: ["resources"] },
|
|
651
794
|
{ id: "internal", title: "Internal", entries: ["internal"] }
|
|
652
795
|
],
|
|
653
796
|
children: [
|
|
@@ -713,24 +856,80 @@ export const ROOT_COMMAND = buildNode({
|
|
|
713
856
|
},
|
|
714
857
|
{
|
|
715
858
|
name: "identity",
|
|
716
|
-
summary: "Read the
|
|
859
|
+
summary: "Read the stable runtime identity receipt (build, backend, worker).",
|
|
717
860
|
hidden: true
|
|
718
861
|
},
|
|
719
862
|
{ name: "stop", summary: "Stop the Controller." },
|
|
720
863
|
{ name: "restart", summary: "Restart internal services without stopping tmux sessions." }
|
|
721
864
|
]
|
|
722
865
|
},
|
|
866
|
+
{
|
|
867
|
+
name: "execution",
|
|
868
|
+
summary: "Read-only execution history audit.",
|
|
869
|
+
sections: [{
|
|
870
|
+
id: "reports",
|
|
871
|
+
title: "Commands",
|
|
872
|
+
entries: ["audit"]
|
|
873
|
+
}],
|
|
874
|
+
children: [
|
|
875
|
+
{
|
|
876
|
+
name: "audit",
|
|
877
|
+
summary: "Report Runs, wakes, Sessions, Reviews, Integrations, and telemetry volume.",
|
|
878
|
+
usage: "yui execution audit [--task <id>] [--since <iso>] [--until <iso>]",
|
|
879
|
+
options: ["--task", "--since", "--until"]
|
|
880
|
+
}
|
|
881
|
+
]
|
|
882
|
+
},
|
|
883
|
+
{
|
|
884
|
+
name: "release",
|
|
885
|
+
summary: "Install and activate immutable local runtime releases.",
|
|
886
|
+
sections: [{
|
|
887
|
+
id: "commands",
|
|
888
|
+
title: "Commands",
|
|
889
|
+
entries: ["install", "list", "activate"]
|
|
890
|
+
}],
|
|
891
|
+
children: [
|
|
892
|
+
{
|
|
893
|
+
name: "install",
|
|
894
|
+
summary: "Install a runtime package as an immutable release.",
|
|
895
|
+
usage: "yui release install <source-dir>"
|
|
896
|
+
},
|
|
897
|
+
{ name: "list", summary: "List installed releases and the active pointer." },
|
|
898
|
+
{
|
|
899
|
+
name: "activate",
|
|
900
|
+
summary: "Activate a release via atomic Controller handover.",
|
|
901
|
+
usage: "yui release activate [release-id]"
|
|
902
|
+
}
|
|
903
|
+
]
|
|
904
|
+
},
|
|
905
|
+
{
|
|
906
|
+
name: "resources",
|
|
907
|
+
summary: "Inspect and garbage-collect managed worktrees, deployments, and runtime artifacts.",
|
|
908
|
+
sections: [{ id: "gc", title: "Commands", entries: ["gc"] }],
|
|
909
|
+
children: [
|
|
910
|
+
{
|
|
911
|
+
name: "gc",
|
|
912
|
+
summary: "Plan or apply resource garbage collection.",
|
|
913
|
+
usage: "yui resources gc [--dry-run|--apply|--purge|--restore] [--quarantine-ttl-hours <hours>]",
|
|
914
|
+
options: ["--dry-run", "--apply", "--purge", "--restore", "--quarantine-ttl-hours"]
|
|
915
|
+
}
|
|
916
|
+
]
|
|
917
|
+
},
|
|
723
918
|
{
|
|
724
919
|
name: "config",
|
|
725
920
|
summary: "Inspect or update Yui configuration.",
|
|
726
|
-
sections: [{ id: "manage", title: "Commands", entries: ["show", "set", "review"] }],
|
|
921
|
+
sections: [{ id: "manage", title: "Commands", entries: ["show", "set", "review", "leader-next-action"] }],
|
|
727
922
|
children: [
|
|
728
923
|
{ name: "show", summary: "Show effective Yui configuration." },
|
|
729
924
|
{
|
|
730
925
|
name: "set",
|
|
731
926
|
summary: "Update Yui configuration.",
|
|
732
|
-
usage: "yui config set <--time-zone <IANA timezone> | --reconciliation-interval-seconds <5-300>>",
|
|
733
|
-
options: ["--time-zone", "--reconciliation-interval-seconds"]
|
|
927
|
+
usage: "yui config set <--time-zone <IANA timezone> | --reconciliation-interval-seconds <5-300> | --resources-gc-mode <report|quarantine> | --resources-gc-auto-quarantine <true|false>>",
|
|
928
|
+
options: ["--time-zone", "--reconciliation-interval-seconds", "--resources-gc-mode", "--resources-gc-auto-quarantine"],
|
|
929
|
+
optionValues: {
|
|
930
|
+
"--resources-gc-mode": ["report", "quarantine"],
|
|
931
|
+
"--resources-gc-auto-quarantine": ["true", "false"]
|
|
932
|
+
}
|
|
734
933
|
},
|
|
735
934
|
{
|
|
736
935
|
name: "review",
|
|
@@ -741,12 +940,29 @@ export const ROOT_COMMAND = buildNode({
|
|
|
741
940
|
{
|
|
742
941
|
name: "set",
|
|
743
942
|
summary: "Enable review with a Global Role.",
|
|
744
|
-
usage: "yui config review set --role <global-role> --trigger <always|leader|final>",
|
|
745
|
-
options: ["--role", "--trigger"],
|
|
746
|
-
optionValues: {
|
|
943
|
+
usage: "yui config review set --role <global-role> --trigger <always|leader|final> [--finding-ledger <shadow|enforce>]",
|
|
944
|
+
options: ["--role", "--trigger", "--finding-ledger"],
|
|
945
|
+
optionValues: {
|
|
946
|
+
"--trigger": ["always", "leader", "final"],
|
|
947
|
+
"--finding-ledger": ["shadow", "enforce"]
|
|
948
|
+
}
|
|
747
949
|
},
|
|
748
950
|
{ name: "clear", summary: "Disable global review." }
|
|
749
951
|
]
|
|
952
|
+
},
|
|
953
|
+
{
|
|
954
|
+
name: "leader-next-action",
|
|
955
|
+
summary: "Configure the Leader next-action/duplicate-guard mode.",
|
|
956
|
+
sections: [{ id: "manage", title: "Commands", entries: ["show", "set", "clear"] }],
|
|
957
|
+
children: [
|
|
958
|
+
{ name: "show", summary: "Show the Leader next-action mode." },
|
|
959
|
+
{
|
|
960
|
+
name: "set",
|
|
961
|
+
summary: "Set the Leader next-action mode (display|warn|enforce).",
|
|
962
|
+
usage: "yui config leader-next-action set <display|warn|enforce>"
|
|
963
|
+
},
|
|
964
|
+
{ name: "clear", summary: "Reset to the default display mode." }
|
|
965
|
+
]
|
|
750
966
|
}
|
|
751
967
|
]
|
|
752
968
|
},
|
|
@@ -896,12 +1112,23 @@ export const ROOT_COMMAND = buildNode({
|
|
|
896
1112
|
name: "task",
|
|
897
1113
|
summary: "Manage Tasks, WorkItems, Agent Runs, and integration.",
|
|
898
1114
|
sections: [
|
|
899
|
-
{ id: "lifecycle", title: "Lifecycle", entries: ["create", "project", "update", "activate", "complete", "reopen", "retire", "list", "show", "context", "archive", "rebuild", "history", "replace", "reconcile"] },
|
|
900
|
-
{ id: "collaboration", title: "Collaboration", entries: ["message", "input", "work", "run", "review", "integration", "role", "enter"] },
|
|
1115
|
+
{ id: "lifecycle", title: "Lifecycle", entries: ["create", "project", "update", "activate", "complete", "reopen", "retire", "list", "show", "context", "next-action", "archive", "rebuild", "history", "replace", "reconcile"] },
|
|
1116
|
+
{ id: "collaboration", title: "Collaboration", entries: ["message", "input", "grant", "workflow", "work", "run", "review", "integration", "role", "enter", "overlap", "change-set"] },
|
|
901
1117
|
{ id: "knowledge", title: "Task Knowledge", entries: ["brief", "decision", "milestone", "event"] }
|
|
902
1118
|
],
|
|
903
1119
|
children: taskChildren
|
|
904
1120
|
},
|
|
1121
|
+
{
|
|
1122
|
+
name: "job",
|
|
1123
|
+
summary: "Start, inspect, cancel, or acknowledge a Controller-managed DurableJob.",
|
|
1124
|
+
sections: [{ id: "manage", title: "Commands", entries: ["start", "get", "cancel", "acknowledge"] }],
|
|
1125
|
+
children: [
|
|
1126
|
+
{ name: "start", summary: "Start a DurableJob for build, test, package, or Integration checks.", usage: "yui job start --task <id> --project <project> --head <sha> --workspace <dir> --step <name>=<command> [--step ...] [--owner task|work-item:<id>|integration-attempt:<id>] [--env <k=v>...]" },
|
|
1127
|
+
{ name: "get", summary: "Show a DurableJob record and its terminal result.", usage: "yui job get --task <id> --job <job-id>" },
|
|
1128
|
+
{ name: "cancel", summary: "Request cancellation of a running or queued DurableJob.", usage: "yui job cancel --task <id> --job <job-id>" },
|
|
1129
|
+
{ name: "acknowledge", summary: "Acknowledge an unknown-needs-attention DurableJob so Task lifecycle gates can proceed.", usage: "yui job acknowledge --task <id> --job <job-id>" }
|
|
1130
|
+
]
|
|
1131
|
+
},
|
|
905
1132
|
{
|
|
906
1133
|
name: "jobs",
|
|
907
1134
|
summary: "Inspect scheduler wake and recovery records.",
|
|
@@ -911,6 +1138,17 @@ export const ROOT_COMMAND = buildNode({
|
|
|
911
1138
|
{ name: "retry", summary: "Retry a failed Leader recovery.", usage: "yui jobs retry <id>" }
|
|
912
1139
|
]
|
|
913
1140
|
},
|
|
1141
|
+
{
|
|
1142
|
+
name: "telemetry",
|
|
1143
|
+
summary: "Inspect and compact the bounded provider-progress sidecar.",
|
|
1144
|
+
sections: [{ id: "manage", title: "Commands", entries: ["status", "prune", "compact", "read"] }],
|
|
1145
|
+
children: [
|
|
1146
|
+
{ name: "status", summary: "Show sidecar health, row counts, and retention settings.", usage: "yui telemetry status" },
|
|
1147
|
+
{ name: "prune", summary: "Apply terminal retention and active-Run caps.", usage: "yui telemetry prune [--task <id>] [--keep <n>] [--dry-run]" },
|
|
1148
|
+
{ name: "compact", summary: "Fold legacy semantic progress events into a staged Home's sidecar.", usage: "yui telemetry compact --from <home> --staged <dir> [--keep <n>] [--dry-run]" },
|
|
1149
|
+
{ name: "read", summary: "Page through retained progress rows or read a Run aggregate.", usage: "yui telemetry read --task <id> [--run <id>] [--aggregate] [--limit <n>] [--offset <n>]" }
|
|
1150
|
+
]
|
|
1151
|
+
},
|
|
914
1152
|
{
|
|
915
1153
|
name: "internal",
|
|
916
1154
|
summary: "Internal Yui callbacks.",
|
|
@@ -241,6 +241,14 @@ function activateAndVerify(ports, staged, home, storageBackupPath, lifecycle, pa
|
|
|
241
241
|
: postSwitchRecoveryAction(home, storageBackupPath),
|
|
242
242
|
recoverable: false,
|
|
243
243
|
version: staged.version,
|
|
244
|
+
// P1-1 (rr22): ANY failure to start or authenticate the replacement
|
|
245
|
+
// Controller leaves the lifecycle handoff unresolved — not just
|
|
246
|
+
// UPDATE_CONTROLLER_UNKNOWN_ACTIVE. Even when the old identity is
|
|
247
|
+
// restored below, the running Controller is not the replacement the
|
|
248
|
+
// step promised, so the adapter must persist the step as unknown
|
|
249
|
+
// rather than letting a controller-home query confirm it on binary
|
|
250
|
+
// health alone.
|
|
251
|
+
controllerOwnershipUnknown: true,
|
|
244
252
|
...(storageBackupPath === undefined ? {} : { storageBackupPath })
|
|
245
253
|
};
|
|
246
254
|
// An ownership-unknown mismatch is a live-process blocker, not a
|
package/dist/cli/updatePorts.js
CHANGED
|
@@ -30,30 +30,89 @@
|
|
|
30
30
|
* can resolve the true state.
|
|
31
31
|
*/
|
|
32
32
|
import { spawnSync } from "node:child_process";
|
|
33
|
-
import { existsSync, mkdtempSync, readFileSync, realpathSync, rmSync } from "node:fs";
|
|
33
|
+
import { accessSync, constants, existsSync, mkdtempSync, readFileSync, realpathSync, rmSync } from "node:fs";
|
|
34
34
|
import { tmpdir } from "node:os";
|
|
35
|
-
import { dirname, isAbsolute, join, resolve } from "node:path";
|
|
35
|
+
import { delimiter, dirname, isAbsolute, join, resolve } from "node:path";
|
|
36
36
|
import { fileURLToPath } from "node:url";
|
|
37
37
|
import { runtimeError } from "../errors/cliError.js";
|
|
38
|
+
import { isConcreteVersion } from "../domain/validation.js";
|
|
38
39
|
import { STORAGE_DOCTOR_CHECK_NAMES } from "../doctor/doctor.js";
|
|
39
40
|
import { inspectStorageSchema } from "../storage/storageSchema.js";
|
|
40
41
|
import { correlateUpgradeReceipt } from "../storage/upgrade/upgradeOrchestrator.js";
|
|
41
42
|
import { readSwitchProgress } from "../storage/upgrade/switchProgress.js";
|
|
42
43
|
const PACKAGE_NAME = "@zq-silk/yui";
|
|
43
44
|
const PACKAGE_SPEC = `${PACKAGE_NAME}@latest`;
|
|
45
|
+
function resolveExecutable(command, environmentPath) {
|
|
46
|
+
if (isAbsolute(command))
|
|
47
|
+
return command;
|
|
48
|
+
for (const directory of (environmentPath ?? "").split(delimiter)) {
|
|
49
|
+
if (directory.length === 0)
|
|
50
|
+
continue;
|
|
51
|
+
const candidate = resolve(directory, command);
|
|
52
|
+
try {
|
|
53
|
+
accessSync(candidate, constants.X_OK);
|
|
54
|
+
return candidate;
|
|
55
|
+
}
|
|
56
|
+
catch {
|
|
57
|
+
// Keep walking PATH; callers fail closed when no executable is found.
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
return undefined;
|
|
61
|
+
}
|
|
62
|
+
function failedSpawnResult(message) {
|
|
63
|
+
return {
|
|
64
|
+
pid: undefined,
|
|
65
|
+
output: [null, Buffer.alloc(0), Buffer.from(message)],
|
|
66
|
+
stdout: Buffer.alloc(0),
|
|
67
|
+
stderr: Buffer.from(message),
|
|
68
|
+
status: 127,
|
|
69
|
+
signal: null,
|
|
70
|
+
error: new Error(message)
|
|
71
|
+
};
|
|
72
|
+
}
|
|
44
73
|
/** Build the real ports. `spawn` is injectable so tests avoid real installs. */
|
|
45
74
|
export function createUpdatePorts(environment, spawn = spawnSync, stagingRoot = tmpdir()) {
|
|
75
|
+
// The production adapter must not let a later PATH change select a
|
|
76
|
+
// different npm during staging, activation, or recovery. Test doubles are
|
|
77
|
+
// intentionally left untouched so deterministic tests can dispatch on the
|
|
78
|
+
// stable command name `npm`.
|
|
79
|
+
const trustedNpm = spawn === spawnSync
|
|
80
|
+
? resolveExecutable("npm", environment.PATH)
|
|
81
|
+
: "npm";
|
|
82
|
+
const run = (command, args, options) => {
|
|
83
|
+
if (command !== "npm")
|
|
84
|
+
return spawn(command, args, options);
|
|
85
|
+
if (trustedNpm === undefined) {
|
|
86
|
+
return failedSpawnResult("Unable to resolve trusted executable: npm");
|
|
87
|
+
}
|
|
88
|
+
return spawn(trustedNpm, args, options);
|
|
89
|
+
};
|
|
46
90
|
// Keep the exact verified artifact in memory for this update attempt. This is
|
|
47
91
|
// deliberately not persisted as a retry or recovery protocol.
|
|
48
92
|
let verifiedActivatedBinary;
|
|
49
93
|
let verifiedActivatedVersion;
|
|
50
|
-
const stopReplacementController = (home, pid) => (stopReplacementControllerForUpdate(home, pid, environment,
|
|
94
|
+
const stopReplacementController = (home, pid) => (stopReplacementControllerForUpdate(home, pid, environment, run));
|
|
51
95
|
return {
|
|
52
|
-
stage() {
|
|
96
|
+
stage(version) {
|
|
97
|
+
// A caller that names a version (the release workflow, which freezes the
|
|
98
|
+
// exact version in its plan) installs THAT version — never a moving
|
|
99
|
+
// `latest` that could resolve to a different build than the one the
|
|
100
|
+
// plan authorized. A non-concrete value fails closed rather than being
|
|
101
|
+
// interpolated into an install spec. An omitted version keeps the
|
|
102
|
+
// interactive `yui update` behavior of staging latest.
|
|
103
|
+
const spec = version === undefined
|
|
104
|
+
? PACKAGE_SPEC
|
|
105
|
+
: isConcreteVersion(version)
|
|
106
|
+
? `${PACKAGE_NAME}@${version.trim()}`
|
|
107
|
+
: null;
|
|
108
|
+
if (spec === null) {
|
|
109
|
+
throw runtimeError(`Refusing to stage a non-concrete version (${String(version)}): only an exact `
|
|
110
|
+
+ "major.minor.patch version can be pinned for an update.");
|
|
111
|
+
}
|
|
53
112
|
const stagingPath = mkdtempSync(join(stagingRoot, "yui-update-stage-"));
|
|
54
113
|
let ownsStaging = true;
|
|
55
114
|
try {
|
|
56
|
-
const result =
|
|
115
|
+
const result = run("npm", ["install", "--global", "--prefix", stagingPath, spec], { cwd: process.cwd(), env: environment, shell: false, stdio: "inherit" });
|
|
57
116
|
assertSpawnOk(result, "stage the new package");
|
|
58
117
|
const binaryPath = join(stagingPath, "bin", "yui");
|
|
59
118
|
// Resolve the EXACT version that was staged (from the staged install's own
|
|
@@ -61,7 +120,7 @@ export function createUpdatePorts(environment, spawn = spawnSync, stagingRoot =
|
|
|
61
120
|
// version, FAIL the stage (R2-F1): we must never fall back to a bare
|
|
62
121
|
// `@latest`, which would let activation promote — and verify wave through —
|
|
63
122
|
// a different build than the one that passed preflight.
|
|
64
|
-
const version = resolveStagedVersion(stagingPath, binaryPath, environment,
|
|
123
|
+
const version = resolveStagedVersion(stagingPath, binaryPath, environment, run);
|
|
65
124
|
if (version === null) {
|
|
66
125
|
throw runtimeError("Failed to resolve the exact staged package version (neither the staged package.json "
|
|
67
126
|
+ "nor `yui --json version` returned a concrete version). Refusing to proceed with a "
|
|
@@ -79,11 +138,11 @@ export function createUpdatePorts(environment, spawn = spawnSync, stagingRoot =
|
|
|
79
138
|
}
|
|
80
139
|
},
|
|
81
140
|
preflight(staged, home) {
|
|
82
|
-
const result =
|
|
141
|
+
const result = run(staged.binaryPath, ["--json", "upgrade", "--update-preflight"], { cwd: process.cwd(), env: { ...environment, YUI_HOME: home }, shell: false });
|
|
83
142
|
return interpretPreflight(result);
|
|
84
143
|
},
|
|
85
144
|
activateStorage(staged, home) {
|
|
86
|
-
const result =
|
|
145
|
+
const result = run(staged.binaryPath, ["--json", "upgrade"], {
|
|
87
146
|
cwd: process.cwd(),
|
|
88
147
|
// The parent update process captures/stops/drains the old Controller
|
|
89
148
|
// before invoking the staged child. Mark this internal call so the
|
|
@@ -105,12 +164,12 @@ export function createUpdatePorts(environment, spawn = spawnSync, stagingRoot =
|
|
|
105
164
|
// `@latest` (R2-F1: `stage` guarantees `staged.version` is a concrete
|
|
106
165
|
// version, so there is no `latest` sentinel to fall back to).
|
|
107
166
|
const spec = `${PACKAGE_NAME}@${staged.version}`;
|
|
108
|
-
const result =
|
|
167
|
+
const result = run("npm", ["install", "--global", spec], { cwd: process.cwd(), env: environment, shell: false, stdio: "inherit" });
|
|
109
168
|
assertSpawnOk(result, "activate the new binary");
|
|
110
169
|
},
|
|
111
170
|
verify(staged, home) {
|
|
112
171
|
// Verify the ACTUALLY-ACTIVATED global binary, not the staging path (P1-3).
|
|
113
|
-
const activeBinary = resolveGlobalBinary(environment,
|
|
172
|
+
const activeBinary = resolveGlobalBinary(environment, run);
|
|
114
173
|
if (activeBinary === null || !existsSync(activeBinary)) {
|
|
115
174
|
throw runtimeError("Post-update health check failed: could not locate the activated global `yui` binary.");
|
|
116
175
|
}
|
|
@@ -122,7 +181,7 @@ export function createUpdatePorts(environment, spawn = spawnSync, stagingRoot =
|
|
|
122
181
|
// "exited with status 5". We therefore parse+validate the structured storage
|
|
123
182
|
// health first: only a valid success envelope with every expected storage
|
|
124
183
|
// check present-and-ok, no blocking checks, AND exit 0 is healthy.
|
|
125
|
-
const doctor =
|
|
184
|
+
const doctor = run(activeBinary, ["--json", "doctor"], { cwd: process.cwd(), env: { ...environment, YUI_HOME: home }, shell: false });
|
|
126
185
|
assertDoctorStorageHealthy(doctor);
|
|
127
186
|
// 2) Confirm the activated binary's identity matches the staged artifact.
|
|
128
187
|
// `staged.version` is always a concrete version (R2-F1), so we REQUIRE the
|
|
@@ -624,8 +683,12 @@ function assertActivatedControllerIdentity(identity, activatedBinary, activatedV
|
|
|
624
683
|
+ "runtime/entrypoint; refusing readiness.");
|
|
625
684
|
}
|
|
626
685
|
}
|
|
627
|
-
/**
|
|
628
|
-
|
|
686
|
+
/**
|
|
687
|
+
* Resolve the Controller entrypoint beside the activated package's CLI.
|
|
688
|
+
* Exported so the release workflow's recovery query can apply the exact same
|
|
689
|
+
* entrypoint derivation as the production startup identity check (P1-1, rr23).
|
|
690
|
+
*/
|
|
691
|
+
export function activatedControllerEntrypoint(activatedBinary) {
|
|
629
692
|
let resolvedBinary;
|
|
630
693
|
try {
|
|
631
694
|
resolvedBinary = realpathSync(activatedBinary);
|
|
@@ -934,15 +997,6 @@ function resolveStagedVersion(stagingPath, binaryPath, environment, spawn) {
|
|
|
934
997
|
// with a successful, concrete version.
|
|
935
998
|
return resolveBinaryVersion(binaryPath, environment, spawn);
|
|
936
999
|
}
|
|
937
|
-
/**
|
|
938
|
-
* True for a concrete, pinnable package version — a semver-shaped `X.Y.Z` with an
|
|
939
|
-
* optional pre-release/build suffix. Rejects dist-tag sentinels (`latest`, `next`,
|
|
940
|
-
* …), empty/whitespace, and anything not anchored to a numeric `major.minor.patch`
|
|
941
|
-
* so a sentinel can never be spliced into an activation spec (R3-F1).
|
|
942
|
-
*/
|
|
943
|
-
function isConcreteVersion(value) {
|
|
944
|
-
return /^\d+\.\d+\.\d+(?:[-+][0-9A-Za-z.-]+)?$/.test(value.trim());
|
|
945
|
-
}
|
|
946
1000
|
/** Read a CONCRETE `version` from a package.json, or `null` when absent/non-concrete. */
|
|
947
1001
|
function readVersionFromPackageJson(path) {
|
|
948
1002
|
try {
|