@ashlr/hub 3.2.6 → 3.3.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/CHANGELOG.md +290 -0
- package/README.md +4 -1
- package/dist/build-identity.json +2 -2
- package/dist/cli/activation.d.ts +41 -0
- package/dist/cli/activation.js +485 -0
- package/dist/cli/activation.js.map +1 -0
- package/dist/cli/audit.js +18 -0
- package/dist/cli/audit.js.map +1 -1
- package/dist/cli/backlog.d.ts +0 -5
- package/dist/cli/backlog.js +18 -0
- package/dist/cli/backlog.js.map +1 -1
- package/dist/cli/comms.js +20 -0
- package/dist/cli/comms.js.map +1 -1
- package/dist/cli/completions.d.ts +6 -1
- package/dist/cli/completions.js +37 -11
- package/dist/cli/completions.js.map +1 -1
- package/dist/cli/conductor-permit.d.ts +1 -0
- package/dist/cli/conductor-permit.js +122 -0
- package/dist/cli/conductor-permit.js.map +1 -0
- package/dist/cli/daemon.js +10 -3
- package/dist/cli/daemon.js.map +1 -1
- package/dist/cli/demo.d.ts +0 -27
- package/dist/cli/demo.js +17 -0
- package/dist/cli/demo.js.map +1 -1
- package/dist/cli/goals.d.ts +2 -0
- package/dist/cli/goals.js +90 -5
- package/dist/cli/goals.js.map +1 -1
- package/dist/cli/help.js +21 -3
- package/dist/cli/help.js.map +1 -1
- package/dist/cli/inbox.d.ts +14 -2
- package/dist/cli/inbox.js +137 -23
- package/dist/cli/inbox.js.map +1 -1
- package/dist/cli/index.d.ts +1 -0
- package/dist/cli/index.js +16 -0
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/loop.js +26 -5
- package/dist/cli/loop.js.map +1 -1
- package/dist/cli/onboard.d.ts +0 -13
- package/dist/cli/onboard.js +20 -0
- package/dist/cli/onboard.js.map +1 -1
- package/dist/cli/resources.js +15 -0
- package/dist/cli/resources.js.map +1 -1
- package/dist/cli/run.js +1 -1
- package/dist/cli/run.js.map +1 -1
- package/dist/cli/sandbox.js +43 -0
- package/dist/cli/sandbox.js.map +1 -1
- package/dist/cli/tui.d.ts +0 -6
- package/dist/cli/tui.js +14 -0
- package/dist/cli/tui.js.map +1 -1
- package/dist/core/comms/elon-dialogue.js +16 -7
- package/dist/core/comms/elon-dialogue.js.map +1 -1
- package/dist/core/config.js +70 -0
- package/dist/core/config.js.map +1 -1
- package/dist/core/daemon/activation-permit.d.ts +357 -33
- package/dist/core/daemon/activation-permit.js +1585 -183
- package/dist/core/daemon/activation-permit.js.map +1 -1
- package/dist/core/daemon/goal-conductor-permit-operator.d.ts +75 -0
- package/dist/core/daemon/goal-conductor-permit-operator.js +842 -0
- package/dist/core/daemon/goal-conductor-permit-operator.js.map +1 -0
- package/dist/core/daemon/loop.d.ts +13 -1
- package/dist/core/daemon/loop.js +294 -29
- package/dist/core/daemon/loop.js.map +1 -1
- package/dist/core/daemon/runtime-activation-resident-start-protocol.d.ts +141 -0
- package/dist/core/daemon/runtime-activation-resident-start-protocol.js +462 -0
- package/dist/core/daemon/runtime-activation-resident-start-protocol.js.map +1 -0
- package/dist/core/daemon/runtime-activation-resident-start-runtime.d.ts +27 -0
- package/dist/core/daemon/runtime-activation-resident-start-runtime.js +28 -0
- package/dist/core/daemon/runtime-activation-resident-start-runtime.js.map +1 -0
- package/dist/core/daemon/runtime-activation-stopped-consumer.d.ts +139 -0
- package/dist/core/daemon/runtime-activation-stopped-consumer.js +995 -0
- package/dist/core/daemon/runtime-activation-stopped-consumer.js.map +1 -0
- package/dist/core/daemon/runtime-activation-stopped-runtime.d.ts +36 -0
- package/dist/core/daemon/runtime-activation-stopped-runtime.js +134 -0
- package/dist/core/daemon/runtime-activation-stopped-runtime.js.map +1 -0
- package/dist/core/daemon/runtime-activation-transaction.d.ts +41 -17
- package/dist/core/daemon/runtime-activation-transaction.js +80 -11
- package/dist/core/daemon/runtime-activation-transaction.js.map +1 -1
- package/dist/core/daemon/runtime-release-dependency-inventory.js +1 -0
- package/dist/core/daemon/runtime-release-dependency-inventory.js.map +1 -1
- package/dist/core/daemon/service-install-authority.d.ts +0 -4
- package/dist/core/daemon/service-install-authority.js +18 -3
- package/dist/core/daemon/service-install-authority.js.map +1 -1
- package/dist/core/dashboard.d.ts +10 -0
- package/dist/core/dashboard.js +103 -28
- package/dist/core/dashboard.js.map +1 -1
- package/dist/core/effective-config.js +7 -0
- package/dist/core/effective-config.js.map +1 -1
- package/dist/core/fleet/automerge-pass.js +33 -0
- package/dist/core/fleet/automerge-pass.js.map +1 -1
- package/dist/core/fleet/decisions-ledger.js +35 -12
- package/dist/core/fleet/decisions-ledger.js.map +1 -1
- package/dist/core/fleet/external-skill-git-capture.js +9 -1
- package/dist/core/fleet/external-skill-git-capture.js.map +1 -1
- package/dist/core/fleet/judge-decision-metadata.d.ts +8 -1
- package/dist/core/fleet/judge-decision-metadata.js +14 -1
- package/dist/core/fleet/judge-decision-metadata.js.map +1 -1
- package/dist/core/fleet/manager.d.ts +15 -1
- package/dist/core/fleet/manager.js +108 -30
- package/dist/core/fleet/manager.js.map +1 -1
- package/dist/core/fleet/post-merge-credit.d.ts +134 -9
- package/dist/core/fleet/post-merge-credit.js +350 -10
- package/dist/core/fleet/post-merge-credit.js.map +1 -1
- package/dist/core/fleet/quota.d.ts +74 -7
- package/dist/core/fleet/quota.js +688 -34
- package/dist/core/fleet/quota.js.map +1 -1
- package/dist/core/fleet/self-improve.d.ts +34 -1
- package/dist/core/fleet/self-improve.js +106 -1
- package/dist/core/fleet/self-improve.js.map +1 -1
- package/dist/core/fleet/skill-library.d.ts +21 -2
- package/dist/core/fleet/skill-library.js +54 -12
- package/dist/core/fleet/skill-library.js.map +1 -1
- package/dist/core/fleet/status.js +7 -5
- package/dist/core/fleet/status.js.map +1 -1
- package/dist/core/fleet/worked-ledger.d.ts +22 -0
- package/dist/core/fleet/worked-ledger.js +32 -0
- package/dist/core/fleet/worked-ledger.js.map +1 -1
- package/dist/core/foundry/automerge-scope-policy.d.ts +2 -2
- package/dist/core/foundry/automerge-scope-policy.js +9 -2
- package/dist/core/foundry/automerge-scope-policy.js.map +1 -1
- package/dist/core/goals/advance.js +34 -5
- package/dist/core/goals/advance.js.map +1 -1
- package/dist/core/goals/conductor-quota.d.ts +38 -0
- package/dist/core/goals/conductor-quota.js +110 -0
- package/dist/core/goals/conductor-quota.js.map +1 -0
- package/dist/core/goals/conductor-target.d.ts +24 -0
- package/dist/core/goals/conductor-target.js +35 -0
- package/dist/core/goals/conductor-target.js.map +1 -0
- package/dist/core/goals/conductor.d.ts +15 -0
- package/dist/core/goals/conductor.js +116 -9
- package/dist/core/goals/conductor.js.map +1 -1
- package/dist/core/goals/store.d.ts +29 -5
- package/dist/core/goals/store.js +197 -30
- package/dist/core/goals/store.js.map +1 -1
- package/dist/core/goals/timestamp-repair.d.ts +50 -0
- package/dist/core/goals/timestamp-repair.js +740 -0
- package/dist/core/goals/timestamp-repair.js.map +1 -0
- package/dist/core/inbox/apply.js +5 -2
- package/dist/core/inbox/apply.js.map +1 -1
- package/dist/core/inbox/merge.d.ts +94 -1
- package/dist/core/inbox/merge.js +349 -16
- package/dist/core/inbox/merge.js.map +1 -1
- package/dist/core/integrations/github.d.ts +2 -0
- package/dist/core/integrations/github.js +2 -1
- package/dist/core/integrations/github.js.map +1 -1
- package/dist/core/integrations/pulse-sync.js +13 -4
- package/dist/core/integrations/pulse-sync.js.map +1 -1
- package/dist/core/learn/reflect.d.ts +42 -4
- package/dist/core/learn/reflect.js +56 -5
- package/dist/core/learn/reflect.js.map +1 -1
- package/dist/core/run/best-of-n.d.ts +18 -0
- package/dist/core/run/best-of-n.js +163 -18
- package/dist/core/run/best-of-n.js.map +1 -1
- package/dist/core/run/engines.d.ts +8 -0
- package/dist/core/run/engines.js +9 -1
- package/dist/core/run/engines.js.map +1 -1
- package/dist/core/run/learned-router.js +28 -3
- package/dist/core/run/learned-router.js.map +1 -1
- package/dist/core/run/orchestrator.d.ts +7 -2
- package/dist/core/run/orchestrator.js +140 -60
- package/dist/core/run/orchestrator.js.map +1 -1
- package/dist/core/strategy/goal-planner.js +7 -1
- package/dist/core/strategy/goal-planner.js.map +1 -1
- package/dist/core/swarm/planner.d.ts +2 -2
- package/dist/core/swarm/planner.js +52 -5
- package/dist/core/swarm/planner.js.map +1 -1
- package/dist/core/swarm/rollback.d.ts +11 -0
- package/dist/core/swarm/rollback.js +54 -0
- package/dist/core/swarm/rollback.js.map +1 -1
- package/dist/core/swarm/runner.js +123 -11
- package/dist/core/swarm/runner.js.map +1 -1
- package/dist/core/types.d.ts +139 -1
- package/dist/core/types.js.map +1 -1
- package/dist/core/web/api.js +105 -7
- package/dist/core/web/api.js.map +1 -1
- package/dist/core/web/control.d.ts +20 -0
- package/dist/core/web/control.js +49 -34
- package/dist/core/web/control.js.map +1 -1
- package/dist/core/web/fleet-status-cache.d.ts +68 -0
- package/dist/core/web/fleet-status-cache.js +120 -0
- package/dist/core/web/fleet-status-cache.js.map +1 -0
- package/dist/core/web/public/next/assets/index-CY0U241s.css +1 -0
- package/dist/core/web/public/next/assets/index-Ct0mf8n4.js +69 -0
- package/dist/core/web/public/next/assets/index-Ct0mf8n4.js.map +1 -0
- package/dist/core/web/public/next/index.html +15 -0
- package/dist/release-dependency-inventory.json +1 -1
- package/dist/tui/app.js +2 -2
- package/dist/tui/app.js.map +1 -1
- package/dist/tui/render.d.ts +2 -0
- package/dist/tui/render.js +162 -4
- package/dist/tui/render.js.map +1 -1
- package/docs/RUNTIME_ACTIVATION_AUTHORITY.md +126 -21
- package/docs/contracts/CONTRACT-M521.md +102 -0
- package/package.json +20 -3
- package/schema/config.schema.json +800 -5
- package/node_modules/which/CHANGELOG.md +0 -166
package/dist/core/daemon/loop.js
CHANGED
|
@@ -45,14 +45,14 @@ import { loadQueuedAutonomyItems } from '../portfolio/queued-autonomy.js';
|
|
|
45
45
|
import { acquireDaemonLock, accountDaemonSpendGuard, armDaemonSpendGuard, clearDaemonSpendGuard, DAEMON_SPEND_GUARD_ITEM_CAPACITY, daemonStatePath, loadDaemonState, loadDaemonStateStrict, readDaemonLockOwner, readDaemonSpendGuard, releaseDaemonLock, resetDayIfNeeded, saveDaemonStateResult, upgradeLegacyDaemonSpendGuard, } from './state.js';
|
|
46
46
|
import { acquireLocalStoreLock, releaseLocalStoreLock, } from '../fleet/local-store-lock.js';
|
|
47
47
|
import { readDaemonActivity, writeDaemonActivity, } from './activity.js';
|
|
48
|
-
import { consumeDaemonActivationPermit, isDaemonActivationCapability, } from './activation-permit.js';
|
|
48
|
+
import { consumeDaemonActivationPermit, daemonActivationScopeGranted, isDaemonActivationCapability, } from './activation-permit.js';
|
|
49
49
|
import { nullSink } from '../run/streaming.js';
|
|
50
50
|
import { createOuterAttemptIdentity } from '../fleet/attempt-identity.js';
|
|
51
51
|
import { runSwarm } from '../swarm/runner.js';
|
|
52
52
|
import { runGoal } from '../run/orchestrator.js';
|
|
53
53
|
import { scopeFromWorkItem } from '../run/delegation-scope.js';
|
|
54
54
|
import { generatedRepairCandidateAllowed, generatedRepairExecutionBackendAllowed, inspectGeneratedRepairRouteFeasibility, routeBackend, } from '../fleet/router.js';
|
|
55
|
-
import { withinLimit,
|
|
55
|
+
import { recordUse, reserveFleetQuotaUse, reserveFleetQuotaUses, withinLimit, } from '../fleet/quota.js';
|
|
56
56
|
import { engineTierOf } from '../run/sandboxed-engine.js';
|
|
57
57
|
import { resolveEngineSpec } from '../run/engine-registry.js';
|
|
58
58
|
import { subscriptionAllows, isSubscriptionEngine } from '../fleet/subscription-usage.js'; // M80
|
|
@@ -93,7 +93,7 @@ import { observeShadowSkills } from '../fleet/skill-shadow-observer.js';
|
|
|
93
93
|
import { selectWorkQueueCoordinator } from '../seams/work-queue-coordinator.js';
|
|
94
94
|
// M220: verdict-feedback sweep — feed judge rejections back to the ledger so
|
|
95
95
|
// re-clogging items (e.g. "CI is failing") are suppressed for the cooldown window.
|
|
96
|
-
import { GENERATED_REPAIR_DISPATCH_BLOCKED_COOLDOWN_MS, latestWorkedEventForKeys, recordOutcome as recordWorkedOutcome, sweepJudgedProposals, workedEventIsCooling, } from '../fleet/worked-ledger.js';
|
|
96
|
+
import { DEFAULT_MAX_REJECTION_ATTEMPTS, GENERATED_REPAIR_DISPATCH_BLOCKED_COOLDOWN_MS, latestWorkedEventForKeys, recordOutcome as recordWorkedOutcome, rejectionAttemptCount, sweepJudgedProposals, workedEventIsCooling, } from '../fleet/worked-ledger.js';
|
|
97
97
|
import { blockingPendingProposalsForBacklog, pendingProposalItemKeysForBacklog, workItemCoverageKey, } from '../fleet/proposal-matching.js';
|
|
98
98
|
import { loadConfig } from '../config.js';
|
|
99
99
|
import { hostname as osHostname } from 'node:os';
|
|
@@ -727,6 +727,13 @@ function generatedRepairShouldSkip(workedEvents, policy) {
|
|
|
727
727
|
const latest = latestWorkedEventForKeys(workedEvents, policy.itemIds);
|
|
728
728
|
if (!latest)
|
|
729
729
|
return false;
|
|
730
|
+
// Attempt cap: cooldown alone only delays a retry — after enough windows
|
|
731
|
+
// elapse the same rejected item comes back forever. Once it has racked up
|
|
732
|
+
// DEFAULT_MAX_REJECTION_ATTEMPTS real judge rejections, park it instead of
|
|
733
|
+
// continuing to re-dispatch (see workItemAttemptCapExceeded/summarizeSelectionBlockers
|
|
734
|
+
// for the operator-visible counter).
|
|
735
|
+
if (workItemAttemptCapExceeded(workedEvents, policy))
|
|
736
|
+
return true;
|
|
730
737
|
const override = policy.outcomeCooldownMs?.[latest.outcome];
|
|
731
738
|
if (override !== undefined) {
|
|
732
739
|
const eventMs = Date.parse(latest.ts);
|
|
@@ -734,6 +741,16 @@ function generatedRepairShouldSkip(workedEvents, policy) {
|
|
|
734
741
|
}
|
|
735
742
|
return workedEventIsCooling(latest, policy.cooldownMs);
|
|
736
743
|
}
|
|
744
|
+
/**
|
|
745
|
+
* True once a work item has racked up DEFAULT_MAX_REJECTION_ATTEMPTS real
|
|
746
|
+
* judge rejections (judged-review/judged-noise/judged-decline). Distinct
|
|
747
|
+
* from the time-based cooldown: cooldown expires and the item comes back;
|
|
748
|
+
* an exceeded attempt cap parks the item for good (until something resets
|
|
749
|
+
* its worked-event history, e.g. the item is meaningfully re-authored).
|
|
750
|
+
*/
|
|
751
|
+
function workItemAttemptCapExceeded(workedEvents, policy) {
|
|
752
|
+
return rejectionAttemptCount(workedEvents, policy.itemIds) >= DEFAULT_MAX_REJECTION_ATTEMPTS;
|
|
753
|
+
}
|
|
737
754
|
const RESOURCE_SNAPSHOT_MAX_AGE_MS = 30_000;
|
|
738
755
|
const GENERATED_REPAIR_EMPTY_FAST_COOLDOWN_MS = 30 * 60 * 1000;
|
|
739
756
|
const MAX_GENERATED_REPAIR_DECISION_EVENTS = 20;
|
|
@@ -779,6 +796,26 @@ const LOCAL_ONLY_BACKENDS = new Set(['builtin', 'local-coder', 'ashlrcode', 'aw'
|
|
|
779
796
|
const DRAIN_MODE_TAG_PREFIX = 'drain:';
|
|
780
797
|
const MAX_DRAIN_SELECTED_IDS = 12;
|
|
781
798
|
const MAX_DIAGNOSTIC_RESLICE_DRAIN_LIMIT = 50;
|
|
799
|
+
/**
|
|
800
|
+
* M470: the exact authority the pre-M470 boolean capability check conveyed.
|
|
801
|
+
*
|
|
802
|
+
* Declared here rather than imported from activation-permit.js on purpose:
|
|
803
|
+
* several suites mock that module wholesale, and importing a *value* from it
|
|
804
|
+
* would throw at module load under those mocks. The explicit type annotation
|
|
805
|
+
* still makes tsc fail if `DaemonActivationGrantScope` ever gains a key, so
|
|
806
|
+
* this cannot silently drift into granting more than it should.
|
|
807
|
+
*/
|
|
808
|
+
const LEGACY_PROPOSAL_ONLY_SCOPE = Object.freeze({
|
|
809
|
+
once: true,
|
|
810
|
+
resident: false,
|
|
811
|
+
residentStanding: false,
|
|
812
|
+
conductor: false,
|
|
813
|
+
automerge: false,
|
|
814
|
+
repair: false,
|
|
815
|
+
deploy: false,
|
|
816
|
+
install: false,
|
|
817
|
+
proposalOnly: true,
|
|
818
|
+
});
|
|
782
819
|
/** Schedule advisory post-state work only after a durable resident tick returns. */
|
|
783
820
|
export function scheduleResolutionObserverAfterTick(tickResult, opts, schedule = scheduleResolutionObserverChild, killIsOn = killSwitchOn) {
|
|
784
821
|
if (opts.dryRun || opts.once || tickResult.reason === 'state-persistence-failed' ||
|
|
@@ -1573,7 +1610,8 @@ function recordQueueSelectionAgentAction(fields) {
|
|
|
1573
1610
|
action: 'daemon:selection',
|
|
1574
1611
|
summary: `${lane}: claimed ${fields.claimedItems.length}/${fields.selectedItems.length} ` +
|
|
1575
1612
|
`from ${fields.eligibleItems}/${fields.backlogItems} eligible; ` +
|
|
1576
|
-
`cooldown ${fields.cooldownBlocked}, pending ${fields.pendingBlocked}, route ${fields.routeBlocked}
|
|
1613
|
+
`cooldown ${fields.cooldownBlocked}, pending ${fields.pendingBlocked}, route ${fields.routeBlocked}` +
|
|
1614
|
+
(fields.attemptCapBlocked > 0 ? `, attempt-cap-parked ${fields.attemptCapBlocked}` : ''),
|
|
1577
1615
|
reason: fields.dryRun ? 'dry-run' : fields.claimedItems.length > 0 ? 'selected' : 'no-claim',
|
|
1578
1616
|
...(first?.repo ? { repo: first.repo } : {}),
|
|
1579
1617
|
...(first?.id ? { itemId: boundedText(first.id, 120) } : {}),
|
|
@@ -1593,6 +1631,7 @@ function recordQueueSelectionAgentAction(fields) {
|
|
|
1593
1631
|
cooldownBlocked: fields.cooldownBlocked,
|
|
1594
1632
|
routeBlocked: fields.routeBlocked,
|
|
1595
1633
|
fastRepairCooldown: fields.fastRepairCooldown,
|
|
1634
|
+
attemptCapBlocked: fields.attemptCapBlocked,
|
|
1596
1635
|
rawSelectCount: fields.rawSelectCount,
|
|
1597
1636
|
selectCount: fields.selectCount,
|
|
1598
1637
|
selected: fields.selectedItems.length,
|
|
@@ -1820,6 +1859,10 @@ function noProposalProductionReason(production) {
|
|
|
1820
1859
|
return boundedText(productionReason(production), 160);
|
|
1821
1860
|
}
|
|
1822
1861
|
function noProposalOutcomeFromReason(reason) {
|
|
1862
|
+
if (/\bquota-reservation-refused\b/i.test(reason))
|
|
1863
|
+
return 'gate-blocked';
|
|
1864
|
+
if (/\b(provider-dispatch|candidate configuration refused)\b/i.test(reason))
|
|
1865
|
+
return 'gate-blocked';
|
|
1823
1866
|
if (/\bempty[- ]diff\b/i.test(reason))
|
|
1824
1867
|
return 'empty-diff';
|
|
1825
1868
|
if (/\b(trivial-proposal|trivial|completeness-gate|gate-blocked|gate)\b/i.test(reason))
|
|
@@ -1855,6 +1898,24 @@ function bestOfNCandidateProduction(candidate, authority) {
|
|
|
1855
1898
|
...(metadata?.diffFiles !== undefined ? { diffFiles: metadata.diffFiles } : {}),
|
|
1856
1899
|
...(metadata?.diffLines !== undefined ? { diffLines: metadata.diffLines } : {}),
|
|
1857
1900
|
};
|
|
1901
|
+
if (candidate.quotaReservation && !candidate.quotaReservation.launchAuthorized) {
|
|
1902
|
+
return {
|
|
1903
|
+
production: {
|
|
1904
|
+
outcome: 'gate-blocked',
|
|
1905
|
+
reason: `quota reservation refused: ${candidate.quotaReservation.kind}`,
|
|
1906
|
+
...diff,
|
|
1907
|
+
},
|
|
1908
|
+
};
|
|
1909
|
+
}
|
|
1910
|
+
if (candidate.providerDispatchAttempted === false && candidate.error) {
|
|
1911
|
+
return {
|
|
1912
|
+
production: {
|
|
1913
|
+
outcome: noProposalOutcomeFromReason(candidate.error),
|
|
1914
|
+
reason: candidate.error,
|
|
1915
|
+
...diff,
|
|
1916
|
+
},
|
|
1917
|
+
};
|
|
1918
|
+
}
|
|
1858
1919
|
switch (candidate.proposalDisposition?.kind) {
|
|
1859
1920
|
case 'verification-rejected':
|
|
1860
1921
|
return {
|
|
@@ -1992,6 +2053,8 @@ function bestOfNAuthoritativeNoWinnerProduction(result, n, runId, costUsd, autho
|
|
|
1992
2053
|
}
|
|
1993
2054
|
if (error &&
|
|
1994
2055
|
error !== 'cancelled' &&
|
|
2056
|
+
candidate.providerDispatchAttempted !== false &&
|
|
2057
|
+
candidate.quotaReservation?.launchAuthorized !== false &&
|
|
1995
2058
|
!candidateErrorDuplicatesProposalOutcome(error, candidate.proposalOutcome)) {
|
|
1996
2059
|
addAuthority({ outcome: 'engine-failed', reason: error }, `error:${error}`);
|
|
1997
2060
|
}
|
|
@@ -2817,7 +2880,21 @@ function claimCooldownPolicyForSelectionItem(item, baseCooldownMs, repairRecover
|
|
|
2817
2880
|
*/
|
|
2818
2881
|
export async function tick(cfg, opts) {
|
|
2819
2882
|
const now = new Date().toISOString();
|
|
2820
|
-
|
|
2883
|
+
// A pre-verified scope (resident path) is accepted WITHOUT consuming, since
|
|
2884
|
+
// runDaemon already consumed the capability once on entry. Otherwise fall back
|
|
2885
|
+
// to consuming the capability here, which is the one-shot path.
|
|
2886
|
+
const preVerifiedScope = !opts.dryRun ? opts.activationScope : undefined;
|
|
2887
|
+
const consumedCapability = !opts.dryRun && preVerifiedScope === undefined
|
|
2888
|
+
? isDaemonActivationCapability(opts.activationCapability)
|
|
2889
|
+
: false;
|
|
2890
|
+
// When the capability check passes but no scope object is readable (a caller
|
|
2891
|
+
// or test that supplies only the claim), fall back to the legacy proposal-only
|
|
2892
|
+
// scope — the exact authority the pre-M470 boolean check conveyed. Never widen.
|
|
2893
|
+
const activationScope = preVerifiedScope
|
|
2894
|
+
?? (consumedCapability
|
|
2895
|
+
? (opts.activationCapability?.scope ?? LEGACY_PROPOSAL_ONLY_SCOPE)
|
|
2896
|
+
: undefined);
|
|
2897
|
+
const activationAccepted = !opts.dryRun && activationScope !== undefined;
|
|
2821
2898
|
if (!opts.dryRun && !activationAccepted) {
|
|
2822
2899
|
persistAudit({
|
|
2823
2900
|
action: 'daemon:activation-refused',
|
|
@@ -2834,7 +2911,52 @@ export async function tick(cfg, opts) {
|
|
|
2834
2911
|
reason: 'activation-refused',
|
|
2835
2912
|
};
|
|
2836
2913
|
}
|
|
2837
|
-
|
|
2914
|
+
// A standing `residentStanding` grant is NOT single-use, unlike the
|
|
2915
|
+
// one-shot WeakMap capability that forces `preVerifiedScope` to be trusted
|
|
2916
|
+
// for the run's whole lifetime (see the TickOptions.activationScope doc).
|
|
2917
|
+
// That means it CAN be safely re-checked on every tick with no consumption
|
|
2918
|
+
// side effect — so a resident daemon that started off a standing grant
|
|
2919
|
+
// gets a real "stop the next tick" guarantee from `ashlr activation
|
|
2920
|
+
// revoke --grant <id>`, not just "blocks the next `daemon start`." This is
|
|
2921
|
+
// strictly stronger than what the one-shot capability path can offer; it
|
|
2922
|
+
// cannot interrupt a tick already in flight.
|
|
2923
|
+
if (!opts.dryRun && preVerifiedScope?.residentStanding === true) {
|
|
2924
|
+
const stillGranted = daemonActivationScopeGranted('residentStanding');
|
|
2925
|
+
if (!stillGranted.granted) {
|
|
2926
|
+
persistAudit({
|
|
2927
|
+
action: 'daemon:activation-refused',
|
|
2928
|
+
repo: null,
|
|
2929
|
+
sandboxId: null,
|
|
2930
|
+
summary: `live tick refused: resident standing grant no longer authorized: ${stillGranted.reason}`,
|
|
2931
|
+
result: 'refused',
|
|
2932
|
+
});
|
|
2933
|
+
return {
|
|
2934
|
+
ts: now,
|
|
2935
|
+
itemsConsidered: 0,
|
|
2936
|
+
proposalsCreated: 0,
|
|
2937
|
+
spentUsd: 0,
|
|
2938
|
+
reason: 'activation-refused',
|
|
2939
|
+
};
|
|
2940
|
+
}
|
|
2941
|
+
}
|
|
2942
|
+
// M470: proposal-only is derived from the GRANTED scope rather than from "an
|
|
2943
|
+
// activation exists at all" — the previous form forced every authorized tick
|
|
2944
|
+
// to proposal-only, so an automerge/repair/deploy grant could never take
|
|
2945
|
+
// effect.
|
|
2946
|
+
//
|
|
2947
|
+
// The `grantedScope` distinction matters: the pre-M470 condition was
|
|
2948
|
+
// `opts.activationCapability !== undefined`, i.e. it keyed off a real permit
|
|
2949
|
+
// object being supplied, NOT off the boolean claim. Callers that pass only a
|
|
2950
|
+
// claim (and the legacy fallback above) must keep the old
|
|
2951
|
+
// `proposalOnlyActivation === false` behavior, otherwise
|
|
2952
|
+
// runAncillaryMaintenance — which early-returns on proposal-only — silently
|
|
2953
|
+
// stops running self-heal, invent, and drain.
|
|
2954
|
+
const grantedScope = preVerifiedScope ?? opts.activationCapability?.scope;
|
|
2955
|
+
const proposalOnlyActivation = activationAccepted &&
|
|
2956
|
+
grantedScope !== undefined &&
|
|
2957
|
+
!grantedScope.automerge &&
|
|
2958
|
+
!grantedScope.repair &&
|
|
2959
|
+
!grantedScope.deploy;
|
|
2838
2960
|
let ownershipLost = false;
|
|
2839
2961
|
const stillOwnsTick = () => {
|
|
2840
2962
|
if (!opts.ownerLock)
|
|
@@ -3410,6 +3532,42 @@ export async function tick(cfg, opts) {
|
|
|
3410
3532
|
console.warn('[ashlr] daemon:tick outcomeWatcher failed:', err?.message ?? err);
|
|
3411
3533
|
}
|
|
3412
3534
|
}
|
|
3535
|
+
// M505: Learning-loop sweeps. Each of these was previously unreachable in a
|
|
3536
|
+
// live tick — rejection learning sat behind `autoMerge.enabled`, post-merge
|
|
3537
|
+
// credit was a stubbed `false`, and reflection was a manual CLI command — so
|
|
3538
|
+
// the fleet observed every run and learned from none of them.
|
|
3539
|
+
if (!stopRequested() && liveCfg.foundry?.['selfImprove'] !== false) {
|
|
3540
|
+
try {
|
|
3541
|
+
const { sweepRejectionLearning } = await import('../fleet/self-improve.js');
|
|
3542
|
+
sweepRejectionLearning(liveCfg);
|
|
3543
|
+
}
|
|
3544
|
+
catch (err) {
|
|
3545
|
+
console.warn('[ashlr] daemon:tick sweepRejectionLearning failed:', err?.message ?? err);
|
|
3546
|
+
}
|
|
3547
|
+
}
|
|
3548
|
+
// Credit is released only by the M505 protocol's own fail-closed checks
|
|
3549
|
+
// (authenticated merge witness, 14-day quiet window, no adverse observation
|
|
3550
|
+
// on the exact event, healthy ledger read). This call only drives the sweep.
|
|
3551
|
+
if (!stopRequested()) {
|
|
3552
|
+
try {
|
|
3553
|
+
const { sweepPostMergeCreditReleases } = await import('../fleet/post-merge-credit.js');
|
|
3554
|
+
sweepPostMergeCreditReleases();
|
|
3555
|
+
}
|
|
3556
|
+
catch (err) {
|
|
3557
|
+
console.warn('[ashlr] daemon:tick sweepPostMergeCreditReleases failed:', err?.message ?? err);
|
|
3558
|
+
}
|
|
3559
|
+
}
|
|
3560
|
+
// Low cadence, matching the counterfactual-replay pattern above: reflection
|
|
3561
|
+
// persists a snapshot and distills playbooks, and does not need every tick.
|
|
3562
|
+
if (!stopRequested() && state.ticks.length % 20 === 0) {
|
|
3563
|
+
try {
|
|
3564
|
+
const { runReflectionCycle } = await import('../learn/reflect.js');
|
|
3565
|
+
await runReflectionCycle(liveCfg);
|
|
3566
|
+
}
|
|
3567
|
+
catch (err) {
|
|
3568
|
+
console.warn('[ashlr] daemon:tick runReflectionCycle failed:', err?.message ?? err);
|
|
3569
|
+
}
|
|
3570
|
+
}
|
|
3413
3571
|
// M189: Regression sentinel — detect regressions introduced by auto-merge and bisect/revert.
|
|
3414
3572
|
const regressionSentinel = liveCfg.foundry?.regressionSentinel;
|
|
3415
3573
|
if (!stopRequested() &&
|
|
@@ -4194,11 +4352,19 @@ export async function tick(cfg, opts) {
|
|
|
4194
4352
|
let cooldownBlocked = 0;
|
|
4195
4353
|
let routeBlocked = 0;
|
|
4196
4354
|
let fastRepairCooldown = 0;
|
|
4355
|
+
let attemptCapBlocked = 0;
|
|
4197
4356
|
for (const item of items) {
|
|
4198
4357
|
const pending = pendingItemKeys.has(workItemCoverageKey(item));
|
|
4199
4358
|
const itemPolicy = claimCooldownPolicies.get(item.id);
|
|
4200
4359
|
const itemCooldownMs = cooldownMsForSelectionItem(selectionWorkedEvents, itemPolicy);
|
|
4201
4360
|
const cooling = generatedRepairShouldSkip(selectionWorkedEvents, itemPolicy);
|
|
4361
|
+
// Attempt-cap-parked items are already folded into `cooling` (they
|
|
4362
|
+
// must never be dispatched), but they get their own counter so an
|
|
4363
|
+
// operator can tell "will retry after cooldown" apart from "parked
|
|
4364
|
+
// for good — rejected too many times" instead of it being silently
|
|
4365
|
+
// indistinguishable in the tick summary.
|
|
4366
|
+
if (cooling && workItemAttemptCapExceeded(selectionWorkedEvents, itemPolicy))
|
|
4367
|
+
attemptCapBlocked++;
|
|
4202
4368
|
if (pending)
|
|
4203
4369
|
pendingBlocked++;
|
|
4204
4370
|
if (cooling)
|
|
@@ -4214,7 +4380,7 @@ export async function tick(cfg, opts) {
|
|
|
4214
4380
|
fastRepairCooldown++;
|
|
4215
4381
|
}
|
|
4216
4382
|
}
|
|
4217
|
-
return { eligibleItems, pendingBlocked, cooldownBlocked, routeBlocked, fastRepairCooldown };
|
|
4383
|
+
return { eligibleItems, pendingBlocked, cooldownBlocked, routeBlocked, fastRepairCooldown, attemptCapBlocked };
|
|
4218
4384
|
};
|
|
4219
4385
|
const selectRoundRobinCandidates = (items, count) => {
|
|
4220
4386
|
// Group selectable items by repo (score-sorted within each group by buildBacklog).
|
|
@@ -4414,6 +4580,7 @@ export async function tick(cfg, opts) {
|
|
|
4414
4580
|
cooldownBlocked: selectionBlockers.cooldownBlocked,
|
|
4415
4581
|
routeBlocked: selectionBlockers.routeBlocked,
|
|
4416
4582
|
fastRepairCooldown: selectionBlockers.fastRepairCooldown,
|
|
4583
|
+
attemptCapBlocked: selectionBlockers.attemptCapBlocked,
|
|
4417
4584
|
rawSelectCount: selectionTelemetryRawSelectCount,
|
|
4418
4585
|
selectCount: selectionTelemetrySelectCount,
|
|
4419
4586
|
selectedItems: selected,
|
|
@@ -4755,6 +4922,33 @@ export async function tick(cfg, opts) {
|
|
|
4755
4922
|
skipReason: 'queue-lease-lost',
|
|
4756
4923
|
}),
|
|
4757
4924
|
});
|
|
4925
|
+
const quotaRefusalOutcome = (item, attemptId, backend, quota) => {
|
|
4926
|
+
const evidence = [
|
|
4927
|
+
typeof quota.used === 'number' ? `used=${quota.used}` : null,
|
|
4928
|
+
typeof quota.limit === 'number' ? `limit=${quota.limit}` : null,
|
|
4929
|
+
quota.reservationConsumed === true ? 'reservation-consumed=true' : null,
|
|
4930
|
+
].filter((value) => value !== null).join(', ');
|
|
4931
|
+
const detail = evidence ? `${quota.kind} (${evidence})` : quota.kind;
|
|
4932
|
+
return {
|
|
4933
|
+
item,
|
|
4934
|
+
spentUsd: 0,
|
|
4935
|
+
dispatched: false,
|
|
4936
|
+
dispatch: dispatchTrace(item, {
|
|
4937
|
+
backend,
|
|
4938
|
+
tier: engineTierOf(backend, routingCfg),
|
|
4939
|
+
assignedBy: 'quota-reservation',
|
|
4940
|
+
reason: `provider dispatch refused by durable quota authority: ${detail}`,
|
|
4941
|
+
dispatched: false,
|
|
4942
|
+
runId: attemptId,
|
|
4943
|
+
trajectoryId: `run:${attemptId}`,
|
|
4944
|
+
skipReason: `quota-${quota.kind}`,
|
|
4945
|
+
production: {
|
|
4946
|
+
outcome: 'gate-blocked',
|
|
4947
|
+
reason: `quota reservation refused: ${detail}`,
|
|
4948
|
+
},
|
|
4949
|
+
}),
|
|
4950
|
+
};
|
|
4951
|
+
};
|
|
4758
4952
|
const tasks = workedSet.map((item, _taskIdx) => {
|
|
4759
4953
|
const attemptId = attemptIds.get(item.id);
|
|
4760
4954
|
const leaseController = leaseAbortControllers.get(item.id);
|
|
@@ -5520,6 +5714,8 @@ export async function tick(cfg, opts) {
|
|
|
5520
5714
|
}
|
|
5521
5715
|
}
|
|
5522
5716
|
let bonBillable = null;
|
|
5717
|
+
let bonLaunchMarked = false;
|
|
5718
|
+
let bonDispatchStarted = false;
|
|
5523
5719
|
let runState;
|
|
5524
5720
|
if (fanOut) {
|
|
5525
5721
|
// Route through runBestOfN; use its winner's underlying runState.
|
|
@@ -5531,16 +5727,6 @@ export async function tick(cfg, opts) {
|
|
|
5531
5727
|
if (!stillOwnsTick())
|
|
5532
5728
|
throw new Error('daemon lock ownership lost before best-of-n launch');
|
|
5533
5729
|
beginQueueExecution();
|
|
5534
|
-
recordUse(backend);
|
|
5535
|
-
recordDispatchStartAgentAction(item, {
|
|
5536
|
-
ts: new Date().toISOString(), machineId, runId: attemptId, backend: backend,
|
|
5537
|
-
tier: backendTier, model: selectedModel, assignedBy, reason: assignmentReason, mode: 'best-of-n',
|
|
5538
|
-
});
|
|
5539
|
-
if (!stillOwnsTick())
|
|
5540
|
-
throw new Error('daemon lock ownership lost before best-of-n producer start');
|
|
5541
|
-
if (!markGeneratedRepairExecutionLaunched(item)) {
|
|
5542
|
-
throw new Error('generated repair launch reservation could not be persisted');
|
|
5543
|
-
}
|
|
5544
5730
|
return runBestOfN(item, routingCfg, {
|
|
5545
5731
|
n: bestOfN, engine: backend, model: selectedModel,
|
|
5546
5732
|
budget: itemBudget,
|
|
@@ -5549,13 +5735,40 @@ export async function tick(cfg, opts) {
|
|
|
5549
5735
|
workItemId: item.id, workItemGenerationId, workSource: item.source,
|
|
5550
5736
|
delegationScope, attemptId, shadowSkillCards, shadowSkillSelectedAt,
|
|
5551
5737
|
signal: dispatchSignal,
|
|
5738
|
+
reserveQuotaUses: (requests) => reserveFleetQuotaUses(requests, routingCfg),
|
|
5739
|
+
providerDispatchStillAuthorized: () => stillOwnsTick() && !stopRequested() && !dispatchSignal.aborted,
|
|
5740
|
+
beforeCandidateProviderDispatch: () => {
|
|
5741
|
+
if (!stillOwnsTick() || stopRequested() || dispatchSignal.aborted)
|
|
5742
|
+
return false;
|
|
5743
|
+
if (!bonLaunchMarked) {
|
|
5744
|
+
if (!markGeneratedRepairExecutionLaunched(item))
|
|
5745
|
+
return false;
|
|
5746
|
+
bonLaunchMarked = true;
|
|
5747
|
+
}
|
|
5748
|
+
return stillOwnsTick() && !stopRequested() && !dispatchSignal.aborted;
|
|
5749
|
+
},
|
|
5750
|
+
onProviderDispatchStarted: (candidateBackend) => {
|
|
5751
|
+
if (!bonDispatchStarted) {
|
|
5752
|
+
recordDispatchStartAgentAction(item, {
|
|
5753
|
+
ts: new Date().toISOString(), machineId, runId: attemptId, backend: backend,
|
|
5754
|
+
tier: backendTier, model: selectedModel, assignedBy,
|
|
5755
|
+
reason: assignmentReason, mode: 'best-of-n',
|
|
5756
|
+
});
|
|
5757
|
+
bonDispatchStarted = true;
|
|
5758
|
+
}
|
|
5759
|
+
recordUse(candidateBackend);
|
|
5760
|
+
},
|
|
5552
5761
|
});
|
|
5553
5762
|
});
|
|
5554
5763
|
if (!launch.authorized)
|
|
5555
5764
|
return authorityUnavailableOutcome(item, attemptId);
|
|
5556
|
-
dispatched = true;
|
|
5557
|
-
backendDispatch[backend] = (backendDispatch[backend] ?? 0) + 1;
|
|
5558
5765
|
const bonResult = await launch.value;
|
|
5766
|
+
const attemptedCandidates = bonResult.candidates.filter((candidate) => candidate.providerDispatchAttempted === true);
|
|
5767
|
+
dispatched = attemptedCandidates.length > 0;
|
|
5768
|
+
for (const candidate of attemptedCandidates) {
|
|
5769
|
+
const candidateBackend = candidate.engine ?? backend;
|
|
5770
|
+
backendDispatch[candidateBackend] = (backendDispatch[candidateBackend] ?? 0) + 1;
|
|
5771
|
+
}
|
|
5559
5772
|
bonBillable = bonResult.critique.billableCostUsd ?? 0;
|
|
5560
5773
|
if (!stillOwnsTick()) {
|
|
5561
5774
|
swarmSpent = bonBillable;
|
|
@@ -5563,14 +5776,14 @@ export async function tick(cfg, opts) {
|
|
|
5563
5776
|
return {
|
|
5564
5777
|
item,
|
|
5565
5778
|
spentUsd: swarmSpent,
|
|
5566
|
-
dispatched
|
|
5779
|
+
dispatched,
|
|
5567
5780
|
dispatch: dispatchTrace(item, {
|
|
5568
5781
|
backend,
|
|
5569
5782
|
tier: backendTier,
|
|
5570
5783
|
model: selectedModel,
|
|
5571
5784
|
assignedBy,
|
|
5572
5785
|
reason: assignmentReason,
|
|
5573
|
-
dispatched
|
|
5786
|
+
dispatched,
|
|
5574
5787
|
spentUsd: swarmSpent,
|
|
5575
5788
|
runId: attemptId,
|
|
5576
5789
|
trajectoryId: `run:${attemptId}`,
|
|
@@ -5594,6 +5807,10 @@ export async function tick(cfg, opts) {
|
|
|
5594
5807
|
: shadowOnly
|
|
5595
5808
|
? bestOfNShadowOnlyProduction(bestOfN)
|
|
5596
5809
|
: bestOfNNoWinnerProduction(bestOfN));
|
|
5810
|
+
const quotaRefusal = bonResult.candidates.find((candidate) => candidate.quotaReservation && !candidate.quotaReservation.launchAuthorized)?.quotaReservation;
|
|
5811
|
+
if (!dispatched && quotaRefusal?.launchAuthorized === false) {
|
|
5812
|
+
return quotaRefusalOutcome(item, attemptId, quotaRefusal.backend ?? backend, quotaRefusal);
|
|
5813
|
+
}
|
|
5597
5814
|
audit({
|
|
5598
5815
|
action: 'daemon:no-proposal',
|
|
5599
5816
|
repo: item.repo,
|
|
@@ -5604,14 +5821,14 @@ export async function tick(cfg, opts) {
|
|
|
5604
5821
|
return {
|
|
5605
5822
|
item,
|
|
5606
5823
|
spentUsd: swarmSpent,
|
|
5607
|
-
dispatched
|
|
5824
|
+
dispatched,
|
|
5608
5825
|
dispatch: dispatchTrace(item, {
|
|
5609
5826
|
backend,
|
|
5610
5827
|
tier: backendTier,
|
|
5611
5828
|
model: selectedModel,
|
|
5612
5829
|
assignedBy,
|
|
5613
5830
|
reason: `${assignmentReason}; best-of-${bestOfN}: all candidates empty`,
|
|
5614
|
-
dispatched
|
|
5831
|
+
dispatched,
|
|
5615
5832
|
spentUsd: swarmSpent,
|
|
5616
5833
|
runId: attemptId,
|
|
5617
5834
|
trajectoryId: `run:${attemptId}`,
|
|
@@ -5633,7 +5850,10 @@ export async function tick(cfg, opts) {
|
|
|
5633
5850
|
if (!stillOwnsTick())
|
|
5634
5851
|
throw new Error('daemon lock ownership lost before direct launch');
|
|
5635
5852
|
beginQueueExecution();
|
|
5636
|
-
|
|
5853
|
+
const quota = reserveFleetQuotaUse(backend, routingCfg, attemptId);
|
|
5854
|
+
if (!quota.launchAuthorized) {
|
|
5855
|
+
return { kind: 'quota-refused', quota };
|
|
5856
|
+
}
|
|
5637
5857
|
recordDispatchStartAgentAction(item, {
|
|
5638
5858
|
ts: new Date().toISOString(), machineId, runId: attemptId, backend: backend,
|
|
5639
5859
|
tier: backendTier, model: selectedModel, assignedBy, reason: assignmentReason, mode: 'single',
|
|
@@ -5643,19 +5863,24 @@ export async function tick(cfg, opts) {
|
|
|
5643
5863
|
if (!markGeneratedRepairExecutionLaunched(item)) {
|
|
5644
5864
|
throw new Error('generated repair launch reservation could not be persisted');
|
|
5645
5865
|
}
|
|
5646
|
-
|
|
5866
|
+
const run = runGoal(goal, dispatchCfg, {
|
|
5647
5867
|
engine: backend, sandboxEngine: true, requireSandbox: true, cwd: item.repo,
|
|
5648
5868
|
budget: itemBudget, tools: true, noMemory: false, runId: attemptId,
|
|
5649
5869
|
...(selectedModel ? { model: selectedModel } : {}),
|
|
5650
5870
|
workItemId: item.id, workItemGenerationId, workSource: item.source, delegationScope,
|
|
5651
5871
|
signal: dispatchSignal,
|
|
5652
5872
|
});
|
|
5873
|
+
recordUse(backend);
|
|
5874
|
+
return { kind: 'started', run };
|
|
5653
5875
|
});
|
|
5654
5876
|
if (!launch.authorized)
|
|
5655
5877
|
return authorityUnavailableOutcome(item, attemptId);
|
|
5878
|
+
if (launch.value.kind === 'quota-refused') {
|
|
5879
|
+
return quotaRefusalOutcome(item, attemptId, backend, launch.value.quota);
|
|
5880
|
+
}
|
|
5656
5881
|
dispatched = true;
|
|
5657
5882
|
backendDispatch[backend] = (backendDispatch[backend] ?? 0) + 1;
|
|
5658
|
-
runState = await launch.value;
|
|
5883
|
+
runState = await launch.value.run;
|
|
5659
5884
|
if (!stillOwnsTick()) {
|
|
5660
5885
|
swarmSpent = isSubscriptionEngine(backend ?? 'builtin') ? 0 : (runState.usage?.estCostUsd ?? 0);
|
|
5661
5886
|
tickSpent += swarmSpent;
|
|
@@ -7036,6 +7261,20 @@ export async function runDaemon(cfg, opts) {
|
|
|
7036
7261
|
process.env['ASHLR_IN_DAEMON'] = '1';
|
|
7037
7262
|
const activationCfg = activation.configSnapshot ?? cfg;
|
|
7038
7263
|
const dcfg = resolveCfg(activationCfg);
|
|
7264
|
+
// M470: verify the capability EXACTLY ONCE, here, and carry the resulting
|
|
7265
|
+
// scope into every tick. isDaemonActivationCapability() consumes on first
|
|
7266
|
+
// call, so verifying per-tick would authorize only the first resident tick
|
|
7267
|
+
// and refuse all subsequent ones. Entering the loop is the authorized act;
|
|
7268
|
+
// each tick then reads the scope as plain data.
|
|
7269
|
+
//
|
|
7270
|
+
// `activation.scope` is the standing-`residentStanding`-grant path
|
|
7271
|
+
// (consumeDaemonActivationPermit tried it first): no WeakMap capability
|
|
7272
|
+
// exists for it at all, since standing grants aren't single-use — tick()
|
|
7273
|
+
// re-checks it live every cycle instead (see its `residentStanding` guard).
|
|
7274
|
+
const runActivationScope = activation.scope
|
|
7275
|
+
?? (!opts.dryRun && activation.capability && isDaemonActivationCapability(activation.capability)
|
|
7276
|
+
? activation.capability.scope
|
|
7277
|
+
: undefined);
|
|
7039
7278
|
// -------------------------------------------------------------------------
|
|
7040
7279
|
// Mark daemon as running.
|
|
7041
7280
|
// -------------------------------------------------------------------------
|
|
@@ -7144,10 +7383,18 @@ export async function runDaemon(cfg, opts) {
|
|
|
7144
7383
|
sandboxId: null,
|
|
7145
7384
|
summary: `daemon started: once=${opts.once}, dryRun=${opts.dryRun}, budget=$${dcfg.dailyBudgetUsd}, ` +
|
|
7146
7385
|
`intervalMs=${dcfg.intervalMs}${opts.drain ? `, drain=${opts.drain}` : ''}` +
|
|
7147
|
-
`${opts.drainLimit ? `, drainLimit=${opts.drainLimit}` : ''}
|
|
7386
|
+
`${opts.drainLimit ? `, drainLimit=${opts.drainLimit}` : ''}` +
|
|
7387
|
+
`${activation.grantId ? `, activatedByStandingGrant=${activation.grantId}` : ''}`,
|
|
7148
7388
|
result: 'ok',
|
|
7149
7389
|
});
|
|
7150
|
-
|
|
7390
|
+
// `!activation.scope` preserves this exactly as it behaved before the
|
|
7391
|
+
// standing-grant path existed: a live (non-dry-run) authorized start always
|
|
7392
|
+
// has EITHER `activation.capability` (one-shot path) OR `activation.scope`
|
|
7393
|
+
// (standing-grant path) set, so this was — and remains — unreachable on any
|
|
7394
|
+
// real authorized run. Written this way rather than changed so the
|
|
7395
|
+
// standing-grant path doesn't silently start exercising a branch nothing
|
|
7396
|
+
// has exercised live before.
|
|
7397
|
+
if (!opts.dryRun && !activation.capability && !activation.scope) {
|
|
7151
7398
|
reconcilePreparedGeneratedRepairReservations();
|
|
7152
7399
|
}
|
|
7153
7400
|
// -------------------------------------------------------------------------
|
|
@@ -7242,7 +7489,7 @@ export async function runDaemon(cfg, opts) {
|
|
|
7242
7489
|
transitionActivity('tick');
|
|
7243
7490
|
const tickResult = await tick(liveCfg, {
|
|
7244
7491
|
dryRun: opts.dryRun,
|
|
7245
|
-
...(
|
|
7492
|
+
...(runActivationScope ? { activationScope: runActivationScope } : {}),
|
|
7246
7493
|
...(opts.drain ? { drain: opts.drain } : {}),
|
|
7247
7494
|
...(opts.drainLimit ? { drainLimit: opts.drainLimit } : {}),
|
|
7248
7495
|
signal: shutdown.signal,
|
|
@@ -7295,6 +7542,10 @@ export async function runDaemon(cfg, opts) {
|
|
|
7295
7542
|
transitionActivity('tick');
|
|
7296
7543
|
const tickResult = await tick(liveCfg, {
|
|
7297
7544
|
dryRun: opts.dryRun,
|
|
7545
|
+
// M470: the resident loop previously passed NO activation at all, so
|
|
7546
|
+
// every live resident tick was refused. It now carries the scope
|
|
7547
|
+
// verified once at runDaemon entry.
|
|
7548
|
+
...(runActivationScope ? { activationScope: runActivationScope } : {}),
|
|
7298
7549
|
...(opts.drain ? { drain: opts.drain } : {}),
|
|
7299
7550
|
...(opts.drainLimit ? { drainLimit: opts.drainLimit } : {}),
|
|
7300
7551
|
signal: shutdown.signal,
|
|
@@ -7590,6 +7841,20 @@ export function buildItemGoal(item) {
|
|
|
7590
7841
|
'(e.g. a platform-gated or intentionally-skipped test, an issue requiring ' +
|
|
7591
7842
|
'product decisions, or already done), make NO changes and stop — ' +
|
|
7592
7843
|
'do not force an edit.');
|
|
7844
|
+
// M507: completion contract — derived from real inbox data showing the two
|
|
7845
|
+
// most common ways an otherwise-reasonable run still gets rejected: (1) the
|
|
7846
|
+
// engine "fixes" a narrow task by deleting/regenerating an entire file (seen
|
|
7847
|
+
// repeatedly on dependency bumps that wiped package.json instead of editing
|
|
7848
|
+
// one version string — judged 'harmful' every time), and (2) a run hits its
|
|
7849
|
+
// budget mid-task and files a half-finished '[partial]' diff that fails
|
|
7850
|
+
// verification and is auto-drained after retries. Neither failure mode is
|
|
7851
|
+
// fixed by a more lenient judge — both are avoidable at generation time.
|
|
7852
|
+
parts.push('Do not delete, regenerate, or wholesale-rewrite a file to make this change — ' +
|
|
7853
|
+
'edit only what the task requires. A diff that removes unrelated dependencies, ' +
|
|
7854
|
+
'scripts, or content is treated as destructive and rejected outright, even when ' +
|
|
7855
|
+
'the stated intent was minor. If you cannot produce a complete diff that passes ' +
|
|
7856
|
+
'this repo’s tests, typecheck, and lint within the available budget, produce NO ' +
|
|
7857
|
+
'diff and stop — a filed partial/unverified change is worse than no proposal.');
|
|
7593
7858
|
return parts.join('\n\n');
|
|
7594
7859
|
}
|
|
7595
7860
|
//# sourceMappingURL=loop.js.map
|