@ashlr/hub 3.2.7 → 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 +268 -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 +35 -11
- package/dist/cli/completions.js.map +1 -1
- 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/help.js +19 -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 +10 -0
- package/dist/cli/index.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/config.js +70 -0
- package/dist/core/config.js.map +1 -1
- package/dist/core/daemon/activation-permit.d.ts +268 -33
- package/dist/core/daemon/activation-permit.js +1111 -106
- package/dist/core/daemon/activation-permit.js.map +1 -1
- package/dist/core/daemon/loop.d.ts +13 -1
- package/dist/core/daemon/loop.js +192 -9
- 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/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 +1 -0
- 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/store.js +18 -4
- package/dist/core/goals/store.js.map +1 -1
- 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/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/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 +86 -60
- package/dist/core/run/orchestrator.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 +94 -8
- package/dist/core/swarm/runner.js.map +1 -1
- package/dist/core/types.d.ts +100 -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 +44 -32
- 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/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,7 +45,7 @@ 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';
|
|
@@ -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,
|
|
@@ -2841,7 +2880,21 @@ function claimCooldownPolicyForSelectionItem(item, baseCooldownMs, repairRecover
|
|
|
2841
2880
|
*/
|
|
2842
2881
|
export async function tick(cfg, opts) {
|
|
2843
2882
|
const now = new Date().toISOString();
|
|
2844
|
-
|
|
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;
|
|
2845
2898
|
if (!opts.dryRun && !activationAccepted) {
|
|
2846
2899
|
persistAudit({
|
|
2847
2900
|
action: 'daemon:activation-refused',
|
|
@@ -2858,7 +2911,52 @@ export async function tick(cfg, opts) {
|
|
|
2858
2911
|
reason: 'activation-refused',
|
|
2859
2912
|
};
|
|
2860
2913
|
}
|
|
2861
|
-
|
|
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;
|
|
2862
2960
|
let ownershipLost = false;
|
|
2863
2961
|
const stillOwnsTick = () => {
|
|
2864
2962
|
if (!opts.ownerLock)
|
|
@@ -3434,6 +3532,42 @@ export async function tick(cfg, opts) {
|
|
|
3434
3532
|
console.warn('[ashlr] daemon:tick outcomeWatcher failed:', err?.message ?? err);
|
|
3435
3533
|
}
|
|
3436
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
|
+
}
|
|
3437
3571
|
// M189: Regression sentinel — detect regressions introduced by auto-merge and bisect/revert.
|
|
3438
3572
|
const regressionSentinel = liveCfg.foundry?.regressionSentinel;
|
|
3439
3573
|
if (!stopRequested() &&
|
|
@@ -4218,11 +4352,19 @@ export async function tick(cfg, opts) {
|
|
|
4218
4352
|
let cooldownBlocked = 0;
|
|
4219
4353
|
let routeBlocked = 0;
|
|
4220
4354
|
let fastRepairCooldown = 0;
|
|
4355
|
+
let attemptCapBlocked = 0;
|
|
4221
4356
|
for (const item of items) {
|
|
4222
4357
|
const pending = pendingItemKeys.has(workItemCoverageKey(item));
|
|
4223
4358
|
const itemPolicy = claimCooldownPolicies.get(item.id);
|
|
4224
4359
|
const itemCooldownMs = cooldownMsForSelectionItem(selectionWorkedEvents, itemPolicy);
|
|
4225
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++;
|
|
4226
4368
|
if (pending)
|
|
4227
4369
|
pendingBlocked++;
|
|
4228
4370
|
if (cooling)
|
|
@@ -4238,7 +4380,7 @@ export async function tick(cfg, opts) {
|
|
|
4238
4380
|
fastRepairCooldown++;
|
|
4239
4381
|
}
|
|
4240
4382
|
}
|
|
4241
|
-
return { eligibleItems, pendingBlocked, cooldownBlocked, routeBlocked, fastRepairCooldown };
|
|
4383
|
+
return { eligibleItems, pendingBlocked, cooldownBlocked, routeBlocked, fastRepairCooldown, attemptCapBlocked };
|
|
4242
4384
|
};
|
|
4243
4385
|
const selectRoundRobinCandidates = (items, count) => {
|
|
4244
4386
|
// Group selectable items by repo (score-sorted within each group by buildBacklog).
|
|
@@ -4438,6 +4580,7 @@ export async function tick(cfg, opts) {
|
|
|
4438
4580
|
cooldownBlocked: selectionBlockers.cooldownBlocked,
|
|
4439
4581
|
routeBlocked: selectionBlockers.routeBlocked,
|
|
4440
4582
|
fastRepairCooldown: selectionBlockers.fastRepairCooldown,
|
|
4583
|
+
attemptCapBlocked: selectionBlockers.attemptCapBlocked,
|
|
4441
4584
|
rawSelectCount: selectionTelemetryRawSelectCount,
|
|
4442
4585
|
selectCount: selectionTelemetrySelectCount,
|
|
4443
4586
|
selectedItems: selected,
|
|
@@ -7118,6 +7261,20 @@ export async function runDaemon(cfg, opts) {
|
|
|
7118
7261
|
process.env['ASHLR_IN_DAEMON'] = '1';
|
|
7119
7262
|
const activationCfg = activation.configSnapshot ?? cfg;
|
|
7120
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);
|
|
7121
7278
|
// -------------------------------------------------------------------------
|
|
7122
7279
|
// Mark daemon as running.
|
|
7123
7280
|
// -------------------------------------------------------------------------
|
|
@@ -7226,10 +7383,18 @@ export async function runDaemon(cfg, opts) {
|
|
|
7226
7383
|
sandboxId: null,
|
|
7227
7384
|
summary: `daemon started: once=${opts.once}, dryRun=${opts.dryRun}, budget=$${dcfg.dailyBudgetUsd}, ` +
|
|
7228
7385
|
`intervalMs=${dcfg.intervalMs}${opts.drain ? `, drain=${opts.drain}` : ''}` +
|
|
7229
|
-
`${opts.drainLimit ? `, drainLimit=${opts.drainLimit}` : ''}
|
|
7386
|
+
`${opts.drainLimit ? `, drainLimit=${opts.drainLimit}` : ''}` +
|
|
7387
|
+
`${activation.grantId ? `, activatedByStandingGrant=${activation.grantId}` : ''}`,
|
|
7230
7388
|
result: 'ok',
|
|
7231
7389
|
});
|
|
7232
|
-
|
|
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) {
|
|
7233
7398
|
reconcilePreparedGeneratedRepairReservations();
|
|
7234
7399
|
}
|
|
7235
7400
|
// -------------------------------------------------------------------------
|
|
@@ -7324,7 +7489,7 @@ export async function runDaemon(cfg, opts) {
|
|
|
7324
7489
|
transitionActivity('tick');
|
|
7325
7490
|
const tickResult = await tick(liveCfg, {
|
|
7326
7491
|
dryRun: opts.dryRun,
|
|
7327
|
-
...(
|
|
7492
|
+
...(runActivationScope ? { activationScope: runActivationScope } : {}),
|
|
7328
7493
|
...(opts.drain ? { drain: opts.drain } : {}),
|
|
7329
7494
|
...(opts.drainLimit ? { drainLimit: opts.drainLimit } : {}),
|
|
7330
7495
|
signal: shutdown.signal,
|
|
@@ -7377,6 +7542,10 @@ export async function runDaemon(cfg, opts) {
|
|
|
7377
7542
|
transitionActivity('tick');
|
|
7378
7543
|
const tickResult = await tick(liveCfg, {
|
|
7379
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 } : {}),
|
|
7380
7549
|
...(opts.drain ? { drain: opts.drain } : {}),
|
|
7381
7550
|
...(opts.drainLimit ? { drainLimit: opts.drainLimit } : {}),
|
|
7382
7551
|
signal: shutdown.signal,
|
|
@@ -7672,6 +7841,20 @@ export function buildItemGoal(item) {
|
|
|
7672
7841
|
'(e.g. a platform-gated or intentionally-skipped test, an issue requiring ' +
|
|
7673
7842
|
'product decisions, or already done), make NO changes and stop — ' +
|
|
7674
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.');
|
|
7675
7858
|
return parts.join('\n\n');
|
|
7676
7859
|
}
|
|
7677
7860
|
//# sourceMappingURL=loop.js.map
|