@phyzess/pappra 0.1.0-beta.13 → 0.1.0-beta.18
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 +134 -15
- package/assets/integrations/skills/pappra/SKILL.md +28 -7
- package/assets/integrations/skills/pappra/references/collaboration.md +633 -0
- package/assets/integrations/skills/pappra/references/environment.md +360 -0
- package/assets/integrations/skills/pappra/references/hosts.md +20 -0
- package/assets/integrations/skills/pappra/references/operations.md +36 -5
- package/dist/adapters/pi/recovery-extension.d.ts +65 -0
- package/dist/adapters/pi/recovery-extension.js +238 -0
- package/dist/adapters/pi/session-transition-extension.d.ts +30 -0
- package/dist/adapters/pi/session-transition-extension.js +265 -0
- package/dist/app/automatic-handoff-events.d.ts +26 -0
- package/dist/app/automatic-handoff-events.js +134 -0
- package/dist/app/bounded-file.d.ts +2 -0
- package/dist/app/bounded-file.js +2 -0
- package/dist/app/capability-evidence/claude.d.ts +37 -0
- package/dist/app/capability-evidence/claude.js +206 -0
- package/dist/app/capability-evidence/codex.d.ts +35 -0
- package/dist/app/capability-evidence/codex.js +193 -0
- package/dist/app/capability-evidence/index.d.ts +33 -0
- package/dist/app/capability-evidence/index.js +117 -0
- package/dist/app/capability-evidence/pi.d.ts +73 -0
- package/dist/app/capability-evidence/pi.js +669 -0
- package/dist/app/capability-evidence/shared.d.ts +69 -0
- package/dist/app/capability-evidence/shared.js +195 -0
- package/dist/app/collab-auto-handoff.d.ts +55 -0
- package/dist/app/collab-auto-handoff.js +384 -0
- package/dist/app/collab-bindings.d.ts +57 -0
- package/dist/app/collab-bindings.js +1006 -0
- package/dist/app/collab-bootstrap-transport.d.ts +163 -0
- package/dist/app/collab-bootstrap-transport.js +769 -0
- package/dist/app/collab-bootstrap.d.ts +137 -0
- package/dist/app/collab-bootstrap.js +3455 -0
- package/dist/app/collab-handoff.d.ts +12 -0
- package/dist/app/collab-handoff.js +511 -0
- package/dist/app/collab-messages.d.ts +2 -0
- package/dist/app/collab-messages.js +2 -0
- package/dist/app/collab-notify.d.ts +156 -0
- package/dist/app/collab-notify.js +1208 -0
- package/dist/app/collab-preflight.d.ts +34 -0
- package/dist/app/collab-preflight.js +96 -0
- package/dist/app/collab-recovery-executor.d.ts +161 -0
- package/dist/app/collab-recovery-executor.js +1153 -0
- package/dist/app/collab-recovery-projection.d.ts +103 -0
- package/dist/app/collab-recovery-projection.js +659 -0
- package/dist/app/collab-recovery.d.ts +25 -0
- package/dist/app/collab-recovery.js +429 -0
- package/dist/app/collab-resources.d.ts +16 -0
- package/dist/app/collab-resources.js +278 -0
- package/dist/app/collab-role-events.d.ts +22 -0
- package/dist/app/collab-role-events.js +329 -0
- package/dist/app/collab-role-orchestration.d.ts +53 -0
- package/dist/app/collab-role-orchestration.js +601 -0
- package/dist/app/collab-role-plan.d.ts +81 -0
- package/dist/app/collab-role-plan.js +504 -0
- package/dist/app/collab-role-repair.d.ts +109 -0
- package/dist/app/collab-role-repair.js +821 -0
- package/dist/app/collab-rollover.d.ts +263 -0
- package/dist/app/collab-rollover.js +1112 -0
- package/dist/app/collab-route.d.ts +64 -0
- package/dist/app/collab-route.js +240 -0
- package/dist/app/collab-session-transition.d.ts +221 -0
- package/dist/app/collab-session-transition.js +3334 -0
- package/dist/app/collab-state.d.ts +7 -0
- package/dist/app/collab-state.js +162 -0
- package/dist/app/collab-task-store.d.ts +22 -0
- package/dist/app/collab-task-store.js +204 -0
- package/dist/app/collab-work-execution.d.ts +127 -0
- package/dist/app/collab-work-execution.js +1027 -0
- package/dist/app/collab-work-plan.d.ts +87 -0
- package/dist/app/collab-work-plan.js +437 -0
- package/dist/app/collaboration.d.ts +8 -2
- package/dist/app/collaboration.js +101 -12
- package/dist/app/doctor.js +108 -5
- package/dist/app/host-mcp.js +34 -8
- package/dist/app/host-probe.js +4 -4
- package/dist/app/init.d.ts +58 -4
- package/dist/app/init.js +768 -155
- package/dist/app/managed-pi-bootstrap-admission.d.ts +14 -0
- package/dist/app/managed-pi-bootstrap-admission.js +104 -0
- package/dist/app/managed-pi-bootstrap-refresh.d.ts +10 -0
- package/dist/app/managed-pi-bootstrap-refresh.js +78 -0
- package/dist/app/managed-pi-delivery.d.ts +8 -0
- package/dist/app/managed-pi-delivery.js +110 -0
- package/dist/app/managed-pi-handoff-receiver.d.ts +15 -0
- package/dist/app/managed-pi-handoff-receiver.js +263 -0
- package/dist/app/managed-pi-handoff-reference.d.ts +3 -0
- package/dist/app/managed-pi-handoff-reference.js +60 -0
- package/dist/app/managed-pi-herdr.d.ts +21 -0
- package/dist/app/managed-pi-herdr.js +185 -0
- package/dist/app/managed-pi-initial-admission.d.ts +28 -0
- package/dist/app/managed-pi-initial-admission.js +416 -0
- package/dist/app/managed-pi-initial-launch.d.ts +5 -0
- package/dist/app/managed-pi-initial-launch.js +61 -0
- package/dist/app/managed-pi-native-observation.d.ts +36 -0
- package/dist/app/managed-pi-native-observation.js +191 -0
- package/dist/app/managed-pi-readiness.d.ts +29 -0
- package/dist/app/managed-pi-readiness.js +288 -0
- package/dist/app/managed-pi-repair-stop.d.ts +21 -0
- package/dist/app/managed-pi-repair-stop.js +371 -0
- package/dist/app/managed-pi-resume-admission.d.ts +32 -0
- package/dist/app/managed-pi-resume-admission.js +618 -0
- package/dist/app/managed-pi-retirement.d.ts +28 -0
- package/dist/app/managed-pi-retirement.js +112 -0
- package/dist/app/managed-pi-sdk-control.d.ts +7 -0
- package/dist/app/managed-pi-sdk-control.js +169 -0
- package/dist/app/managed-pi-sdk-runtime.d.ts +3 -0
- package/dist/app/managed-pi-sdk-runtime.js +483 -0
- package/dist/app/managed-pi-source-admission.d.ts +14 -0
- package/dist/app/managed-pi-source-admission.js +323 -0
- package/dist/app/managed-pi-transition-host.d.ts +36 -0
- package/dist/app/managed-pi-transition-host.js +292 -0
- package/dist/app/managed-pi.d.ts +54 -0
- package/dist/app/managed-pi.js +248 -0
- package/dist/app/shell-readiness.d.ts +66 -0
- package/dist/app/shell-readiness.js +368 -0
- package/dist/app/task-pin.d.ts +2 -23
- package/dist/app/task-pin.js +2 -113
- package/dist/cli/commands/collaboration-binding.d.ts +13 -0
- package/dist/cli/commands/collaboration-binding.js +118 -0
- package/dist/cli/commands/collaboration-bootstrap.d.ts +10 -0
- package/dist/cli/commands/collaboration-bootstrap.js +84 -0
- package/dist/cli/commands/collaboration-capabilities.d.ts +13 -0
- package/dist/cli/commands/collaboration-capabilities.js +127 -0
- package/dist/cli/commands/collaboration-handoff.d.ts +13 -0
- package/dist/cli/commands/collaboration-handoff.js +102 -0
- package/dist/cli/commands/collaboration-message.d.ts +17 -0
- package/dist/cli/commands/collaboration-message.js +177 -0
- package/dist/cli/commands/collaboration-plan.d.ts +8 -0
- package/dist/cli/commands/collaboration-plan.js +108 -0
- package/dist/cli/commands/collaboration-preflight.d.ts +10 -0
- package/dist/cli/commands/collaboration-preflight.js +167 -0
- package/dist/cli/commands/collaboration-recovery-run.d.ts +46 -0
- package/dist/cli/commands/collaboration-recovery-run.js +224 -0
- package/dist/cli/commands/collaboration-recovery.d.ts +11 -0
- package/dist/cli/commands/collaboration-recovery.js +104 -0
- package/dist/cli/commands/collaboration-resources.d.ts +11 -0
- package/dist/cli/commands/collaboration-resources.js +96 -0
- package/dist/cli/commands/collaboration-role-plan.d.ts +9 -0
- package/dist/cli/commands/collaboration-role-plan.js +69 -0
- package/dist/cli/commands/collaboration-role-run.d.ts +11 -0
- package/dist/cli/commands/collaboration-role-run.js +128 -0
- package/dist/cli/commands/collaboration-rollover.d.ts +10 -0
- package/dist/cli/commands/collaboration-rollover.js +103 -0
- package/dist/cli/commands/collaboration-state.d.ts +10 -0
- package/dist/cli/commands/collaboration-state.js +96 -0
- package/dist/cli/commands/collaboration-transition.d.ts +10 -0
- package/dist/cli/commands/collaboration-transition.js +75 -0
- package/dist/cli/commands/collaboration-work-run.d.ts +34 -0
- package/dist/cli/commands/collaboration-work-run.js +178 -0
- package/dist/cli/commands/collaboration.d.ts +4 -0
- package/dist/cli/commands/collaboration.js +39 -4
- package/dist/cli/commands/continuity.js +2 -6
- package/dist/cli/commands/docs.d.ts +2 -0
- package/dist/cli/commands/docs.js +89 -15
- package/dist/cli/commands/host.d.ts +7 -1
- package/dist/cli/commands/host.js +122 -7
- package/dist/cli/commands/init.d.ts +20 -3
- package/dist/cli/commands/init.js +897 -232
- package/dist/cli/commands/mcp.d.ts +1 -0
- package/dist/cli/commands/mcp.js +11 -5
- package/dist/cli/commands/repair.d.ts +12 -0
- package/dist/cli/commands/repair.js +41 -0
- package/dist/cli/commands/runtime.d.ts +1 -1
- package/dist/cli/commands/runtime.js +139 -38
- package/dist/cli/commands/session.d.ts +7 -0
- package/dist/cli/commands/session.js +82 -0
- package/dist/cli/commands/task.js +19 -5
- package/dist/cli/run.js +866 -24
- package/dist/code/identity.d.ts +7 -0
- package/dist/code/identity.js +7 -3
- package/dist/code/registration.js +7 -0
- package/dist/code/service.d.ts +9 -0
- package/dist/code/service.js +16 -3
- package/dist/code/text.js +19 -7
- package/dist/collab/admission.d.ts +91 -66
- package/dist/collab/admission.js +21 -6
- package/dist/collab/canonical.d.ts +1 -1
- package/dist/collab/canonical.js +44 -3
- package/dist/collab/envelope.d.ts +2 -2
- package/dist/collab/envelope.js +12 -12
- package/dist/collab/first-work.d.ts +3 -0
- package/dist/collab/first-work.js +15 -0
- package/dist/collab/handoff.d.ts +16 -0
- package/dist/collab/handoff.js +39 -5
- package/dist/collab/index.d.ts +97 -1
- package/dist/collab/index.js +9 -1
- package/dist/collab/messages.js +126 -3
- package/dist/collab/persisted-messages.d.ts +123 -0
- package/dist/collab/persisted-messages.js +496 -0
- package/dist/collab/preflight.d.ts +1 -1
- package/dist/collab/resources.d.ts +13 -3
- package/dist/collab/resources.js +37 -6
- package/dist/collab/rollover.d.ts +311 -0
- package/dist/collab/rollover.js +1024 -0
- package/dist/collab/store.d.ts +1 -0
- package/dist/collab/store.js +1 -4
- package/dist/collab/types.d.ts +2 -354
- package/dist/collab/validation.d.ts +2 -10
- package/dist/collab/validation.js +2 -303
- package/dist/collab/work-plan.d.ts +105 -0
- package/dist/collab/work-plan.js +843 -0
- package/dist/continuity/context.d.ts +11 -2
- package/dist/continuity/context.js +100 -13
- package/dist/continuity/handoff-bundle.d.ts +296 -0
- package/dist/continuity/handoff-bundle.js +477 -0
- package/dist/continuity/index.d.ts +2 -0
- package/dist/continuity/index.js +1 -0
- package/dist/continuity/paths.js +10 -2
- package/dist/continuity/service.js +91 -15
- package/dist/continuity/task-pin.d.ts +24 -0
- package/dist/continuity/task-pin.js +590 -0
- package/dist/doctor.d.ts +1 -0
- package/dist/doctor.js +6 -0
- package/dist/experimental/index.d.ts +13 -0
- package/dist/experimental/index.js +25 -0
- package/dist/experimental/pi.d.ts +4 -0
- package/dist/experimental/pi.js +12 -0
- package/dist/hosts/claude/native.js +1 -1
- package/dist/hosts/codex/native.js +1 -1
- package/dist/hosts/codex/protocol.js +1 -1
- package/dist/hosts/integration/bootstrap-config.d.ts +58 -0
- package/dist/hosts/integration/bootstrap-config.js +254 -0
- package/dist/hosts/integration/bundle-export.d.ts +44 -0
- package/dist/hosts/integration/bundle-export.js +366 -3
- package/dist/hosts/integration/bundle.js +3 -0
- package/dist/hosts/integration/hook.js +36 -9
- package/dist/hosts/integration/pappra-bootstrap-extension.mjs +80 -0
- package/dist/hosts/integration/pappra-extension-runtime.mjs +59 -24631
- package/dist/hosts/integration/pappra-hook.mjs +61 -24749
- package/dist/hosts/integration/pappra-recovery-extension.mjs +259 -0
- package/dist/hosts/integration/pappra-session-transition-extension.mjs +276 -0
- package/dist/hosts/integration/pi-bootstrap-extension.d.ts +36 -0
- package/dist/hosts/integration/pi-bootstrap-extension.js +101 -0
- package/dist/hosts/integration/pi-bootstrap-guard.d.ts +125 -0
- package/dist/hosts/integration/pi-bootstrap-guard.js +438 -0
- package/dist/hosts/integration/pi-bootstrap-runtime.d.ts +63 -0
- package/dist/hosts/integration/pi-bootstrap-runtime.js +223 -0
- package/dist/hosts/integration/pi-native-events.d.ts +112 -0
- package/dist/hosts/integration/pi-native-events.js +348 -0
- package/dist/hosts/integration/pi-rollover-extension.d.ts +166 -0
- package/dist/hosts/integration/pi-rollover-extension.js +431 -0
- package/dist/hosts/integration/recovery-config.d.ts +48 -0
- package/dist/hosts/integration/recovery-config.js +117 -0
- package/dist/hosts/integration/service.d.ts +6 -1
- package/dist/hosts/integration/service.js +485 -35
- package/dist/hosts/integration/support.d.ts +7 -0
- package/dist/hosts/integration/support.js +9 -1
- package/dist/hosts/integration/templates.d.ts +3 -0
- package/dist/hosts/integration/templates.js +26 -3
- package/dist/hosts/pi/managed-host-identity.d.ts +2 -0
- package/dist/hosts/pi/managed-host-identity.js +40 -0
- package/dist/hosts/pi/managed-provider-diagnostics.d.ts +6 -0
- package/dist/hosts/pi/managed-provider-diagnostics.js +184 -0
- package/dist/hosts/pi/managed-sdk-session.d.ts +335 -0
- package/dist/hosts/pi/managed-sdk-session.js +1639 -0
- package/dist/hosts/pi/managed-session-extension.d.ts +20 -0
- package/dist/hosts/pi/managed-session-extension.js +84 -0
- package/dist/hosts/pi/managed-session.d.ts +177 -0
- package/dist/hosts/pi/managed-session.js +1557 -0
- package/dist/hosts/pi/protocol.js +1 -1
- package/dist/hosts/pi/query-result.js +1 -1
- package/dist/hosts/pi/resume-capability.d.ts +41 -0
- package/dist/hosts/pi/resume-capability.js +33 -0
- package/dist/hosts/pi/rpc.d.ts +182 -134
- package/dist/hosts/pi/rpc.js +230 -37
- package/dist/hosts/pilot.d.ts +97 -74
- package/dist/hosts/pilot.js +5 -1
- package/dist/hosts/version.d.ts +3 -0
- package/dist/hosts/version.js +18 -0
- package/dist/index.d.ts +0 -1
- package/dist/index.js +0 -1
- package/dist/infra/bounded-file.d.ts +19 -0
- package/dist/infra/bounded-file.js +66 -0
- package/dist/infra/fs.js +10 -2
- package/dist/infra/git.d.ts +29 -0
- package/dist/infra/git.js +45 -9
- package/dist/infra/json-property.d.ts +2 -0
- package/dist/infra/json-property.js +69 -0
- package/dist/infra/json.d.ts +4 -0
- package/dist/infra/json.js +24 -0
- package/dist/infra/owned-process-group.js +1 -1
- package/dist/infra/private-lock.d.ts +16 -0
- package/dist/infra/private-lock.js +148 -0
- package/dist/infra/private-state.d.ts +7 -0
- package/dist/infra/private-state.js +146 -0
- package/dist/infra/process-identity.d.ts +12 -0
- package/dist/infra/process-identity.js +142 -0
- package/dist/infra/validation.d.ts +5 -0
- package/dist/infra/validation.js +15 -0
- package/dist/init.d.ts +1 -0
- package/dist/init.js +6 -0
- package/dist/install/apply.d.ts +9 -1
- package/dist/install/apply.js +176 -12
- package/dist/install/dependencies.d.ts +38 -0
- package/dist/install/dependencies.js +282 -0
- package/dist/install/ownership.js +30 -1
- package/dist/install/references.d.ts +31 -0
- package/dist/install/references.js +208 -0
- package/dist/install/repair.d.ts +37 -0
- package/dist/install/repair.js +798 -0
- package/dist/install/reset-local.d.ts +16 -0
- package/dist/install/reset-local.js +304 -0
- package/dist/install/rollback.d.ts +3 -0
- package/dist/install/rollback.js +9 -3
- package/dist/install/runtime.d.ts +10 -6
- package/dist/install/runtime.js +299 -59
- package/dist/install/source-fingerprint.d.ts +2 -0
- package/dist/install/source-fingerprint.js +103 -0
- package/dist/knowledge/catalog.d.ts +1 -0
- package/dist/knowledge/catalog.js +143 -10
- package/dist/knowledge/journal.js +17 -7
- package/dist/knowledge/paths.js +2 -1
- package/dist/knowledge/refs.js +4 -0
- package/dist/knowledge/register-batch.d.ts +4 -0
- package/dist/knowledge/register-batch.js +225 -0
- package/dist/knowledge/relocate.d.ts +5 -0
- package/dist/knowledge/relocate.js +92 -0
- package/dist/knowledge/scan.js +103 -47
- package/dist/knowledge/service.d.ts +3 -0
- package/dist/knowledge/service.js +33 -1
- package/dist/knowledge/write.d.ts +4 -1
- package/dist/knowledge/write.js +1 -1
- package/dist/mcp/server.d.ts +4 -2
- package/dist/mcp/server.js +87 -136
- package/dist/schemas/collaboration-state-validation.d.ts +6 -0
- package/dist/schemas/collaboration-state-validation.js +342 -0
- package/dist/schemas/collaboration-state.d.ts +436 -0
- package/dist/schemas/collaboration-state.js +1 -0
- package/dist/schemas/collaboration.d.ts +335 -0
- package/dist/schemas/collaboration.js +272 -0
- package/dist/schemas/continuity.d.ts +8 -0
- package/dist/schemas/continuity.js +13 -3
- package/dist/schemas/doctor.d.ts +1 -0
- package/dist/schemas/doctor.js +1 -0
- package/dist/schemas/document.d.ts +61 -0
- package/dist/schemas/document.js +9 -0
- package/dist/schemas/errors.d.ts +1 -0
- package/dist/schemas/errors.js +1 -0
- package/dist/schemas/host.d.ts +24 -0
- package/dist/schemas/host.js +19 -0
- package/dist/schemas/install.d.ts +8 -2
- package/dist/schemas/install.js +11 -1
- package/dist/schemas/managed-pi-execution.d.ts +16 -0
- package/dist/schemas/managed-pi-execution.js +2 -0
- package/dist/schemas/outcome.d.ts +9 -5
- package/dist/workspace/config-file.js +1 -3
- package/dist/workspace/documents.js +12 -2
- package/dist/workspace/repositories.d.ts +7 -0
- package/dist/workspace/repositories.js +14 -5
- package/package.json +35 -35
- package/skills/pappra/SKILL.md +28 -7
- package/skills/pappra/references/collaboration.md +633 -0
- package/skills/pappra/references/environment.md +360 -0
- package/skills/pappra/references/hosts.md +20 -0
- package/skills/pappra/references/operations.md +36 -5
- package/dist/app/collab-from.d.ts +0 -35
- package/dist/app/collab-from.js +0 -84
|
@@ -0,0 +1,278 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import { collaborationResourceRequestSchema, } from '../schemas/collaboration.js';
|
|
3
|
+
import { activeClaims, claimResources, checkClaimParticipant, DomainLockHeldError, listDomainRecords, releaseClaim, } from '../collab/resources.js';
|
|
4
|
+
import { readBoundedFile } from './bounded-file.js';
|
|
5
|
+
import { assertTaskStoreCurrent, openTaskStore, validateDomainMutationPaths, validateDomainTaskState, } from './collab-task-store.js';
|
|
6
|
+
export const COLLAB_RESOURCES_SCHEMA = 'pappra/collaboration-resources/1';
|
|
7
|
+
const MAX_REQUEST_BYTES = 1024 * 1024;
|
|
8
|
+
const MAX_RESOURCE_TASKS = 100;
|
|
9
|
+
const MAX_RESOURCE_CLAIMS = 500;
|
|
10
|
+
function invalidRequest(message, nextAction) {
|
|
11
|
+
return {
|
|
12
|
+
ok: false,
|
|
13
|
+
code: 'invalid_request',
|
|
14
|
+
message,
|
|
15
|
+
nextAction,
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Read and strictly validate one bounded resource request. Diagnostic text only
|
|
20
|
+
* names field paths/codes and never echoes request body values.
|
|
21
|
+
*/
|
|
22
|
+
export function loadResourceRequest(file, expectedAction) {
|
|
23
|
+
if (!path.isAbsolute(file)) {
|
|
24
|
+
return invalidRequest('Resource request path must be absolute.', 'Pass an absolute request JSON path.');
|
|
25
|
+
}
|
|
26
|
+
const read = readBoundedFile(file, MAX_REQUEST_BYTES);
|
|
27
|
+
if (!read.ok) {
|
|
28
|
+
return invalidRequest('Resource request must be a readable regular JSON file no larger than 1 MiB.', 'Inspect the request path and file kind; nothing was written.');
|
|
29
|
+
}
|
|
30
|
+
let parsed;
|
|
31
|
+
try {
|
|
32
|
+
parsed = JSON.parse(read.text);
|
|
33
|
+
}
|
|
34
|
+
catch {
|
|
35
|
+
return invalidRequest('Resource request is not valid JSON.', 'Rewrite the request as strict JSON; nothing was written.');
|
|
36
|
+
}
|
|
37
|
+
const validated = collaborationResourceRequestSchema.safeParse(parsed);
|
|
38
|
+
if (!validated.success) {
|
|
39
|
+
const issues = validated.error.issues
|
|
40
|
+
.slice(0, 8)
|
|
41
|
+
.map((issue) => {
|
|
42
|
+
const field = issue.path.length ? issue.path.join('.') : '(root)';
|
|
43
|
+
return `${field}:${issue.code}`;
|
|
44
|
+
})
|
|
45
|
+
.join(', ');
|
|
46
|
+
return invalidRequest(`Resource request does not match the strict schema (${issues || 'invalid shape'}).`, 'Fix the request schema; nothing was written.');
|
|
47
|
+
}
|
|
48
|
+
if (validated.data.action !== expectedAction) {
|
|
49
|
+
return invalidRequest('Resource request action does not match the invoked resource command.', `Use a ${expectedAction} request; nothing was written.`);
|
|
50
|
+
}
|
|
51
|
+
return { ok: true, value: { request: validated.data, path: file } };
|
|
52
|
+
}
|
|
53
|
+
function identityOf(holder) {
|
|
54
|
+
return {
|
|
55
|
+
agent_id: holder.agent_id,
|
|
56
|
+
session_id: holder.session_id,
|
|
57
|
+
harness: holder.harness ?? null,
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
function requestSummary(request) {
|
|
61
|
+
if (request.action === 'claim') {
|
|
62
|
+
return {
|
|
63
|
+
action: request.action,
|
|
64
|
+
holder: identityOf(request.holder),
|
|
65
|
+
access: request.access,
|
|
66
|
+
resource_count: request.resources.length,
|
|
67
|
+
include_shared_git: request.include_shared_git === true,
|
|
68
|
+
subtask_id: request.subtask_id ?? null,
|
|
69
|
+
attempt: request.attempt ?? null,
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
return {
|
|
73
|
+
action: request.action,
|
|
74
|
+
holder: identityOf(request.holder),
|
|
75
|
+
claim_id: request.claim_id,
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
async function openForResource(taskRoot, action) {
|
|
79
|
+
const opened = await openTaskStore(taskRoot);
|
|
80
|
+
if (!opened.ok)
|
|
81
|
+
return opened;
|
|
82
|
+
if (action === 'claim') {
|
|
83
|
+
const domainState = validateDomainTaskState(opened.value.domainRoot);
|
|
84
|
+
if (domainState)
|
|
85
|
+
return domainState;
|
|
86
|
+
const mutationPaths = validateDomainMutationPaths(opened.value.domainRoot);
|
|
87
|
+
if (mutationPaths)
|
|
88
|
+
return mutationPaths;
|
|
89
|
+
}
|
|
90
|
+
return opened;
|
|
91
|
+
}
|
|
92
|
+
async function revalidateForApply(opened, action) {
|
|
93
|
+
const changed = await assertTaskStoreCurrent(opened);
|
|
94
|
+
if (changed !== null)
|
|
95
|
+
return changed;
|
|
96
|
+
if (action === 'claim') {
|
|
97
|
+
const domainState = validateDomainTaskState(opened.domainRoot);
|
|
98
|
+
if (domainState)
|
|
99
|
+
return domainState;
|
|
100
|
+
return validateDomainMutationPaths(opened.domainRoot);
|
|
101
|
+
}
|
|
102
|
+
return null;
|
|
103
|
+
}
|
|
104
|
+
export async function planResourceRequest(taskRoot, request) {
|
|
105
|
+
const opened = await openForResource(taskRoot, request.action);
|
|
106
|
+
if (!opened.ok)
|
|
107
|
+
return opened;
|
|
108
|
+
if (request.action === 'claim') {
|
|
109
|
+
const participant = checkClaimParticipant(opened.value.record, identityOf(request.holder), request.subtask_id ?? null, request.attempt ?? null);
|
|
110
|
+
if (!participant.ok)
|
|
111
|
+
return {
|
|
112
|
+
ok: false,
|
|
113
|
+
code: participant.reason,
|
|
114
|
+
message: 'The resource holder is not authorized for an active task assignment.',
|
|
115
|
+
nextAction: 'Use the current coordinator identity or the matching active assignment and holder.',
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
return {
|
|
119
|
+
ok: true,
|
|
120
|
+
value: {
|
|
121
|
+
schema: COLLAB_RESOURCES_SCHEMA,
|
|
122
|
+
ok: true,
|
|
123
|
+
action: request.action,
|
|
124
|
+
applied: false,
|
|
125
|
+
status: 'planned',
|
|
126
|
+
task_root: opened.value.taskRoot,
|
|
127
|
+
task_id: opened.value.record.task_id,
|
|
128
|
+
domain_root: opened.value.domainRoot,
|
|
129
|
+
request: requestSummary(request),
|
|
130
|
+
limits: ['plan_validates_request_and_namespace_only', 'no_reservation'],
|
|
131
|
+
},
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
export async function applyResourceRequest(taskRoot, request) {
|
|
135
|
+
const opened = await openForResource(taskRoot, request.action);
|
|
136
|
+
if (!opened.ok)
|
|
137
|
+
return opened;
|
|
138
|
+
const changed = await revalidateForApply(opened.value, request.action);
|
|
139
|
+
if (changed)
|
|
140
|
+
return changed;
|
|
141
|
+
if (request.action === 'claim') {
|
|
142
|
+
const resources = request.resources;
|
|
143
|
+
let outcome;
|
|
144
|
+
try {
|
|
145
|
+
outcome = claimResources(opened.value.taskRoot, {
|
|
146
|
+
resources,
|
|
147
|
+
access: request.access,
|
|
148
|
+
holder: identityOf(request.holder),
|
|
149
|
+
note: request.note ?? null,
|
|
150
|
+
includeSharedGit: request.include_shared_git === true,
|
|
151
|
+
subtaskId: request.subtask_id ?? null,
|
|
152
|
+
attempt: request.attempt ?? null,
|
|
153
|
+
requireAssignedHolder: true,
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
catch (error) {
|
|
157
|
+
if (error instanceof DomainLockHeldError)
|
|
158
|
+
return {
|
|
159
|
+
ok: false,
|
|
160
|
+
code: 'domain_lock_held',
|
|
161
|
+
message: 'The resource-domain lock is held; no claim was made.',
|
|
162
|
+
nextAction: 'Retry explicitly after the current domain operation finishes; nothing was mutated.',
|
|
163
|
+
};
|
|
164
|
+
return {
|
|
165
|
+
ok: false,
|
|
166
|
+
code: 'resource_operation_failed',
|
|
167
|
+
message: 'The resource claim could not be completed.',
|
|
168
|
+
nextAction: 'Inspect the task/domain state; no claim is promised.',
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
return {
|
|
172
|
+
ok: true,
|
|
173
|
+
value: {
|
|
174
|
+
schema: COLLAB_RESOURCES_SCHEMA,
|
|
175
|
+
action: 'claim',
|
|
176
|
+
applied: outcome.ok,
|
|
177
|
+
task_root: opened.value.taskRoot,
|
|
178
|
+
task_id: opened.value.record.task_id,
|
|
179
|
+
...outcome,
|
|
180
|
+
},
|
|
181
|
+
};
|
|
182
|
+
}
|
|
183
|
+
let outcome;
|
|
184
|
+
try {
|
|
185
|
+
outcome = releaseClaim(opened.value.taskRoot, {
|
|
186
|
+
claimId: request.claim_id,
|
|
187
|
+
holder: identityOf(request.holder),
|
|
188
|
+
...(request.reason === undefined || request.reason === null
|
|
189
|
+
? {}
|
|
190
|
+
: { reason: request.reason }),
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
catch {
|
|
194
|
+
return {
|
|
195
|
+
ok: false,
|
|
196
|
+
code: 'resource_operation_failed',
|
|
197
|
+
message: 'The resource release could not be completed.',
|
|
198
|
+
nextAction: 'Inspect the task state; nothing is promised to have been released.',
|
|
199
|
+
};
|
|
200
|
+
}
|
|
201
|
+
return {
|
|
202
|
+
ok: true,
|
|
203
|
+
value: {
|
|
204
|
+
schema: COLLAB_RESOURCES_SCHEMA,
|
|
205
|
+
action: 'release',
|
|
206
|
+
applied: outcome.ok,
|
|
207
|
+
task_root: opened.value.taskRoot,
|
|
208
|
+
task_id: opened.value.record.task_id,
|
|
209
|
+
...outcome,
|
|
210
|
+
},
|
|
211
|
+
};
|
|
212
|
+
}
|
|
213
|
+
export async function listTaskResources(taskRoot) {
|
|
214
|
+
const opened = await openTaskStore(taskRoot);
|
|
215
|
+
if (!opened.ok)
|
|
216
|
+
return opened;
|
|
217
|
+
const domainState = validateDomainTaskState(opened.value.domainRoot);
|
|
218
|
+
if (domainState)
|
|
219
|
+
return domainState;
|
|
220
|
+
const records = listDomainRecords(opened.value.domainRoot);
|
|
221
|
+
const unreadable = records
|
|
222
|
+
.filter((record) => record.__unreadable)
|
|
223
|
+
.map((record) => record.task_id);
|
|
224
|
+
const tasks = records
|
|
225
|
+
.map((record) => ({
|
|
226
|
+
task_id: record.task_id,
|
|
227
|
+
claims: activeClaims(record).map((claim) => ({
|
|
228
|
+
...claim,
|
|
229
|
+
task_id: record.task_id,
|
|
230
|
+
})),
|
|
231
|
+
}))
|
|
232
|
+
.filter((task) => task.claims.length > 0);
|
|
233
|
+
const claimCount = tasks.reduce((total, task) => total + task.claims.length, 0);
|
|
234
|
+
let remainingClaims = MAX_RESOURCE_CLAIMS;
|
|
235
|
+
let emittedClaims = 0;
|
|
236
|
+
const taskRows = [];
|
|
237
|
+
for (const task of tasks) {
|
|
238
|
+
if (taskRows.length >= MAX_RESOURCE_TASKS || remainingClaims <= 0)
|
|
239
|
+
break;
|
|
240
|
+
const take = Math.min(task.claims.length, remainingClaims);
|
|
241
|
+
if (take === 0)
|
|
242
|
+
continue;
|
|
243
|
+
taskRows.push({
|
|
244
|
+
...task,
|
|
245
|
+
claims: task.claims.slice(0, take),
|
|
246
|
+
});
|
|
247
|
+
remainingClaims -= take;
|
|
248
|
+
emittedClaims += take;
|
|
249
|
+
}
|
|
250
|
+
const omittedClaims = claimCount - emittedClaims;
|
|
251
|
+
const omittedTasks = tasks.length - taskRows.length;
|
|
252
|
+
const omittedUnreadable = Math.max(0, unreadable.length - MAX_RESOURCE_TASKS);
|
|
253
|
+
return {
|
|
254
|
+
ok: true,
|
|
255
|
+
value: {
|
|
256
|
+
schema: COLLAB_RESOURCES_SCHEMA,
|
|
257
|
+
ok: unreadable.length === 0,
|
|
258
|
+
action: 'list',
|
|
259
|
+
state: unreadable.length ? 'unknown' : 'known',
|
|
260
|
+
domain_id: opened.value.record.domain_id,
|
|
261
|
+
domain_root: opened.value.domainRoot,
|
|
262
|
+
tasks: taskRows,
|
|
263
|
+
unreadable_tasks: unreadable.slice(0, MAX_RESOURCE_TASKS),
|
|
264
|
+
counts: {
|
|
265
|
+
tasks: tasks.length,
|
|
266
|
+
claims: claimCount,
|
|
267
|
+
unreadable_tasks: unreadable.length,
|
|
268
|
+
},
|
|
269
|
+
omitted: {
|
|
270
|
+
tasks: omittedTasks,
|
|
271
|
+
claims: omittedClaims,
|
|
272
|
+
unreadable_tasks: omittedUnreadable,
|
|
273
|
+
},
|
|
274
|
+
truncated: omittedClaims > 0 || omittedTasks > 0 || omittedUnreadable > 0,
|
|
275
|
+
limits: ['reported_domain_claims_only', 'unreadable_state_is_not_empty'],
|
|
276
|
+
},
|
|
277
|
+
};
|
|
278
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { tickRoleOrchestration, type RoleOrchestrationRequest, type RoleOrchestrationResult } from './collab-role-orchestration.js';
|
|
2
|
+
import { openTaskStore, assertTaskStoreCurrent } from './collab-task-store.js';
|
|
3
|
+
export declare const ROLE_RUN_REQUEST_SCHEMA = "pappra/collab-role-orchestration-request/1";
|
|
4
|
+
export declare function loadRoleRunRequest(file: string): RoleOrchestrationRequest;
|
|
5
|
+
export interface RoleEventWatcher {
|
|
6
|
+
refresh(): Promise<void>;
|
|
7
|
+
status(): {
|
|
8
|
+
readonly result: RoleOrchestrationResult | null;
|
|
9
|
+
readonly error: string | null;
|
|
10
|
+
};
|
|
11
|
+
close(): Promise<void>;
|
|
12
|
+
}
|
|
13
|
+
export interface RoleEventDependencies {
|
|
14
|
+
readonly openTaskStore?: typeof openTaskStore;
|
|
15
|
+
readonly assertTaskStoreCurrent?: typeof assertTaskStoreCurrent;
|
|
16
|
+
readonly tick?: typeof tickRoleOrchestration;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Native session events and authoritative task-store changes drive one bounded tick.
|
|
20
|
+
* Watch subscriptions follow request changes. The orchestration journal is never a cause.
|
|
21
|
+
*/
|
|
22
|
+
export declare function watchRoleOrchestration(requestFile: string, onResult?: (result: RoleOrchestrationResult) => void, deps?: RoleEventDependencies): Promise<RoleEventWatcher>;
|
|
@@ -0,0 +1,329 @@
|
|
|
1
|
+
import crypto from 'node:crypto';
|
|
2
|
+
import fs from 'node:fs';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import { readBoundedFile } from './bounded-file.js';
|
|
5
|
+
import { tickRoleOrchestration, } from './collab-role-orchestration.js';
|
|
6
|
+
import { openTaskStore, assertTaskStoreCurrent } from './collab-task-store.js';
|
|
7
|
+
import { canonicalJson } from '../collab/envelope.js';
|
|
8
|
+
import { managedPiRepairDependencies } from './managed-pi-repair-stop.js';
|
|
9
|
+
export const ROLE_RUN_REQUEST_SCHEMA = 'pappra/collab-role-orchestration-request/1';
|
|
10
|
+
export function loadRoleRunRequest(file) {
|
|
11
|
+
const loaded = readBoundedFile(file, 1024 * 1024);
|
|
12
|
+
if (!loaded.ok)
|
|
13
|
+
throw new Error(`role_request_${loaded.code}`);
|
|
14
|
+
let value;
|
|
15
|
+
try {
|
|
16
|
+
value = JSON.parse(loaded.text);
|
|
17
|
+
}
|
|
18
|
+
catch {
|
|
19
|
+
throw new Error('role_request_invalid_json');
|
|
20
|
+
}
|
|
21
|
+
if (typeof value !== 'object' ||
|
|
22
|
+
value === null ||
|
|
23
|
+
Array.isArray(value) ||
|
|
24
|
+
!('schema' in value) ||
|
|
25
|
+
value.schema !== ROLE_RUN_REQUEST_SCHEMA ||
|
|
26
|
+
!('role_plan' in value) ||
|
|
27
|
+
!('execution' in value) ||
|
|
28
|
+
!('limits' in value) ||
|
|
29
|
+
!('event_id' in value) ||
|
|
30
|
+
!('journal_file' in value))
|
|
31
|
+
throw new Error('role_request_invalid');
|
|
32
|
+
return value;
|
|
33
|
+
}
|
|
34
|
+
function safeDirectory(directory) {
|
|
35
|
+
const stat = fs.lstatSync(directory);
|
|
36
|
+
if (!stat.isDirectory() ||
|
|
37
|
+
stat.isSymbolicLink() ||
|
|
38
|
+
fs.realpathSync(directory) !== directory)
|
|
39
|
+
throw new Error('role_event_directory_redirected');
|
|
40
|
+
return stat;
|
|
41
|
+
}
|
|
42
|
+
function buildWatchNames(requestFile, taskRoot, request, pins) {
|
|
43
|
+
const journalFile = path.resolve(request.journal_file);
|
|
44
|
+
const inputs = [
|
|
45
|
+
requestFile,
|
|
46
|
+
path.join(taskRoot, 'task.json'),
|
|
47
|
+
...request.execution.capability_cards.map((card) => card.capabilities_file),
|
|
48
|
+
];
|
|
49
|
+
const watchNames = new Map();
|
|
50
|
+
for (const input of inputs) {
|
|
51
|
+
if (typeof input !== 'string' || !path.isAbsolute(input))
|
|
52
|
+
throw new Error('role_event_path_not_absolute');
|
|
53
|
+
const absolute = path.resolve(input);
|
|
54
|
+
if (absolute === journalFile)
|
|
55
|
+
throw new Error('role_event_journal_input_conflict');
|
|
56
|
+
const directory = path.dirname(absolute);
|
|
57
|
+
const stat = safeDirectory(directory);
|
|
58
|
+
const pin = pins.get(directory);
|
|
59
|
+
if (pin && (pin.device !== stat.dev || pin.inode !== stat.ino))
|
|
60
|
+
throw new Error('role_event_directory_changed');
|
|
61
|
+
if (!pin)
|
|
62
|
+
pins.set(directory, { device: stat.dev, inode: stat.ino });
|
|
63
|
+
const names = watchNames.get(directory) ?? new Set();
|
|
64
|
+
names.add(path.basename(absolute));
|
|
65
|
+
watchNames.set(directory, names);
|
|
66
|
+
}
|
|
67
|
+
return watchNames;
|
|
68
|
+
}
|
|
69
|
+
function sameNames(left, right) {
|
|
70
|
+
return left.size === right.size && [...left].every((name) => right.has(name));
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Native session events and authoritative task-store changes drive one bounded tick.
|
|
74
|
+
* Watch subscriptions follow request changes. The orchestration journal is never a cause.
|
|
75
|
+
*/
|
|
76
|
+
export async function watchRoleOrchestration(requestFile, onResult = () => { }, deps = {}) {
|
|
77
|
+
if (!path.isAbsolute(requestFile))
|
|
78
|
+
throw new Error('role_event_request_path_not_absolute');
|
|
79
|
+
const openStore = deps.openTaskStore ?? openTaskStore;
|
|
80
|
+
const assertCurrent = deps.assertTaskStoreCurrent ?? assertTaskStoreCurrent;
|
|
81
|
+
const tick = deps.tick ?? tickRoleOrchestration;
|
|
82
|
+
const initial = loadRoleRunRequest(requestFile);
|
|
83
|
+
const opened = await openStore(initial.execution.task_root);
|
|
84
|
+
if (!opened.ok)
|
|
85
|
+
throw new Error('role_event_task_unavailable');
|
|
86
|
+
const task = opened.value;
|
|
87
|
+
const taskRoot = task.taskRoot;
|
|
88
|
+
const messagesDirectory = task.messagesDirectory;
|
|
89
|
+
if (path.resolve(initial.execution.task_root) !== path.resolve(taskRoot))
|
|
90
|
+
throw new Error('role_event_task_root_mismatch');
|
|
91
|
+
const messagesRelative = path.relative(taskRoot, messagesDirectory);
|
|
92
|
+
if (messagesRelative === '' ||
|
|
93
|
+
messagesRelative.startsWith('..') ||
|
|
94
|
+
path.isAbsolute(messagesRelative))
|
|
95
|
+
throw new Error('role_event_task_namespace_mismatch');
|
|
96
|
+
const taskRootIdentity = safeDirectory(taskRoot);
|
|
97
|
+
const messagesDirectoryIdentity = safeDirectory(messagesDirectory);
|
|
98
|
+
const namespacePins = {
|
|
99
|
+
taskRoot: { device: taskRootIdentity.dev, inode: taskRootIdentity.ino },
|
|
100
|
+
messagesDirectory: {
|
|
101
|
+
device: messagesDirectoryIdentity.dev,
|
|
102
|
+
inode: messagesDirectoryIdentity.ino,
|
|
103
|
+
},
|
|
104
|
+
};
|
|
105
|
+
const directoryPins = new Map();
|
|
106
|
+
const watchers = new Map();
|
|
107
|
+
let closed = false;
|
|
108
|
+
let running = false;
|
|
109
|
+
let pending = false;
|
|
110
|
+
let last = null;
|
|
111
|
+
let lastError = null;
|
|
112
|
+
let lastCause = null;
|
|
113
|
+
let retryCause = null;
|
|
114
|
+
let lockRetries = 0;
|
|
115
|
+
let retryTimer = null;
|
|
116
|
+
let subscriptionTimer = null;
|
|
117
|
+
let activeJournalFile = path.resolve(initial.journal_file);
|
|
118
|
+
let active = Promise.resolve();
|
|
119
|
+
const syncWatchers = (request) => {
|
|
120
|
+
let changed = false;
|
|
121
|
+
const activeDirectories = new Set([
|
|
122
|
+
path.dirname(path.resolve(requestFile)),
|
|
123
|
+
path.dirname(path.join(taskRoot, 'task.json')),
|
|
124
|
+
...request.execution.capability_cards.map((card) => path.dirname(path.resolve(card.capabilities_file))),
|
|
125
|
+
]);
|
|
126
|
+
for (const directory of directoryPins.keys()) {
|
|
127
|
+
if (!activeDirectories.has(directory))
|
|
128
|
+
directoryPins.delete(directory);
|
|
129
|
+
}
|
|
130
|
+
const desired = buildWatchNames(requestFile, taskRoot, request, directoryPins);
|
|
131
|
+
activeJournalFile = path.resolve(request.journal_file);
|
|
132
|
+
for (const [directory, current] of watchers) {
|
|
133
|
+
const names = desired.get(directory);
|
|
134
|
+
if (!names) {
|
|
135
|
+
current.watcher.close();
|
|
136
|
+
watchers.delete(directory);
|
|
137
|
+
changed = true;
|
|
138
|
+
}
|
|
139
|
+
else if (!sameNames(current.names, names)) {
|
|
140
|
+
current.names = names;
|
|
141
|
+
changed = true;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
for (const [directory, names] of desired) {
|
|
145
|
+
if (watchers.has(directory))
|
|
146
|
+
continue;
|
|
147
|
+
const entry = {
|
|
148
|
+
watcher: fs.watch(directory, (_event, name) => {
|
|
149
|
+
if (closed)
|
|
150
|
+
return;
|
|
151
|
+
const observedName = name?.toString() ?? null;
|
|
152
|
+
const ownJournal = activeJournalFile;
|
|
153
|
+
if (observedName !== null &&
|
|
154
|
+
path.resolve(directory, observedName) === ownJournal)
|
|
155
|
+
return;
|
|
156
|
+
if (observedName === null || entry.names.has(observedName))
|
|
157
|
+
void refresh();
|
|
158
|
+
}),
|
|
159
|
+
names,
|
|
160
|
+
};
|
|
161
|
+
entry.watcher.on('error', () => {
|
|
162
|
+
lastError = 'role_event_watch_failed';
|
|
163
|
+
});
|
|
164
|
+
watchers.set(directory, entry);
|
|
165
|
+
changed = true;
|
|
166
|
+
}
|
|
167
|
+
if (changed) {
|
|
168
|
+
if (subscriptionTimer)
|
|
169
|
+
clearTimeout(subscriptionTimer);
|
|
170
|
+
// Directory subscriptions can miss writes while the OS attaches them.
|
|
171
|
+
// Reconcile once after rebinding; unchanged causes never dispatch again.
|
|
172
|
+
subscriptionTimer = setTimeout(() => {
|
|
173
|
+
subscriptionTimer = null;
|
|
174
|
+
if (!closed)
|
|
175
|
+
void refresh();
|
|
176
|
+
}, 50);
|
|
177
|
+
subscriptionTimer.unref();
|
|
178
|
+
}
|
|
179
|
+
};
|
|
180
|
+
const refresh = () => {
|
|
181
|
+
if (closed)
|
|
182
|
+
return active;
|
|
183
|
+
pending = true;
|
|
184
|
+
if (running)
|
|
185
|
+
return active;
|
|
186
|
+
running = true;
|
|
187
|
+
active = (async () => {
|
|
188
|
+
try {
|
|
189
|
+
while (pending && !closed) {
|
|
190
|
+
pending = false;
|
|
191
|
+
const request = loadRoleRunRequest(requestFile);
|
|
192
|
+
if (path.resolve(request.execution.task_root) !== path.resolve(taskRoot))
|
|
193
|
+
throw new Error('role_event_task_changed');
|
|
194
|
+
syncWatchers(request);
|
|
195
|
+
for (const [directory, pin] of directoryPins) {
|
|
196
|
+
const now = safeDirectory(directory);
|
|
197
|
+
if (now.dev !== pin.device || now.ino !== pin.inode)
|
|
198
|
+
throw new Error('role_event_directory_changed');
|
|
199
|
+
}
|
|
200
|
+
const current = await openStore(taskRoot);
|
|
201
|
+
if (!current.ok)
|
|
202
|
+
throw new Error('role_event_task_unavailable');
|
|
203
|
+
if (path.resolve(current.value.taskRoot) !== path.resolve(taskRoot) ||
|
|
204
|
+
path.resolve(current.value.messagesDirectory) !==
|
|
205
|
+
path.resolve(messagesDirectory))
|
|
206
|
+
throw new Error('role_event_task_namespace_changed');
|
|
207
|
+
const currentTaskRoot = safeDirectory(current.value.taskRoot);
|
|
208
|
+
const currentMessagesDirectory = safeDirectory(current.value.messagesDirectory);
|
|
209
|
+
if (currentTaskRoot.dev !== namespacePins.taskRoot.device ||
|
|
210
|
+
currentTaskRoot.ino !== namespacePins.taskRoot.inode ||
|
|
211
|
+
currentMessagesDirectory.dev !==
|
|
212
|
+
namespacePins.messagesDirectory.device ||
|
|
213
|
+
currentMessagesDirectory.ino !==
|
|
214
|
+
namespacePins.messagesDirectory.inode)
|
|
215
|
+
throw new Error('role_event_task_namespace_changed');
|
|
216
|
+
const unsafe = await assertCurrent(current.value);
|
|
217
|
+
if (unsafe)
|
|
218
|
+
throw new Error('role_event_task_store_changed');
|
|
219
|
+
const record = current.value.record;
|
|
220
|
+
const cardDigests = request.execution.capability_cards.map((card) => {
|
|
221
|
+
const bytes = readBoundedFile(card.capabilities_file, 1024 * 1024);
|
|
222
|
+
return {
|
|
223
|
+
assignment: card.assignment_id,
|
|
224
|
+
digest: bytes.ok ? bytes.digest : bytes.code,
|
|
225
|
+
};
|
|
226
|
+
});
|
|
227
|
+
const cause = crypto
|
|
228
|
+
.createHash('sha256')
|
|
229
|
+
.update(canonicalJson({
|
|
230
|
+
request: {
|
|
231
|
+
role_plan: request.role_plan,
|
|
232
|
+
execution: request.execution,
|
|
233
|
+
journal_file: request.journal_file,
|
|
234
|
+
limits: request.limits,
|
|
235
|
+
repair_policy: request.repair_policy ?? null,
|
|
236
|
+
},
|
|
237
|
+
coordinator: record.coordinator ?? null,
|
|
238
|
+
attempts: record.attempts ?? [],
|
|
239
|
+
messages: record.messages ?? [],
|
|
240
|
+
results: record.results ?? [],
|
|
241
|
+
claims: record.claims ?? [],
|
|
242
|
+
uncertainty: record.uncertainty ?? [],
|
|
243
|
+
dispatch_frozen: record.dispatch_frozen ?? false,
|
|
244
|
+
managed_runtimes: record.managed_pi_runtime_registry ?? null,
|
|
245
|
+
cards: cardDigests,
|
|
246
|
+
}))
|
|
247
|
+
.digest('hex');
|
|
248
|
+
if (cause === lastCause)
|
|
249
|
+
continue;
|
|
250
|
+
lastCause = cause;
|
|
251
|
+
const repair = managedPiRepairDependencies(request);
|
|
252
|
+
last = await tick({
|
|
253
|
+
...request,
|
|
254
|
+
// Assigned-but-undispatched work was previously mistaken for
|
|
255
|
+
// active execution. Reconcile it under the corrected scheduler;
|
|
256
|
+
// durable work intents still prevent any native redelivery.
|
|
257
|
+
event_id: `task-event-v2-${cause}`,
|
|
258
|
+
}, repair ? { repair } : {});
|
|
259
|
+
lastError = null;
|
|
260
|
+
onResult(last);
|
|
261
|
+
if (last.diagnostics.includes('journal_lock_present_or_busy')) {
|
|
262
|
+
if (retryCause !== cause) {
|
|
263
|
+
retryCause = cause;
|
|
264
|
+
lockRetries = 0;
|
|
265
|
+
}
|
|
266
|
+
if (lockRetries < 2 && !retryTimer) {
|
|
267
|
+
lockRetries++;
|
|
268
|
+
// This outcome reserved no tick and caused no native effect.
|
|
269
|
+
// A stale lock remains visible after the bounded retries.
|
|
270
|
+
retryTimer = setTimeout(() => {
|
|
271
|
+
retryTimer = null;
|
|
272
|
+
if (!closed) {
|
|
273
|
+
lastCause = null;
|
|
274
|
+
void refresh();
|
|
275
|
+
}
|
|
276
|
+
}, 50 * lockRetries);
|
|
277
|
+
retryTimer.unref();
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
else {
|
|
281
|
+
if (retryTimer)
|
|
282
|
+
clearTimeout(retryTimer);
|
|
283
|
+
retryTimer = null;
|
|
284
|
+
retryCause = null;
|
|
285
|
+
lockRetries = 0;
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
catch {
|
|
290
|
+
// Never publish filesystem paths, serialized requests, or raw exception details.
|
|
291
|
+
lastError = 'role_event_reconciliation_failed';
|
|
292
|
+
}
|
|
293
|
+
finally {
|
|
294
|
+
running = false;
|
|
295
|
+
if (pending && !closed)
|
|
296
|
+
void refresh();
|
|
297
|
+
}
|
|
298
|
+
})();
|
|
299
|
+
return active;
|
|
300
|
+
};
|
|
301
|
+
try {
|
|
302
|
+
syncWatchers(initial);
|
|
303
|
+
await refresh();
|
|
304
|
+
}
|
|
305
|
+
catch {
|
|
306
|
+
if (subscriptionTimer)
|
|
307
|
+
clearTimeout(subscriptionTimer);
|
|
308
|
+
for (const entry of watchers.values())
|
|
309
|
+
entry.watcher.close();
|
|
310
|
+
throw new Error('role_event_watch_initialization_failed');
|
|
311
|
+
}
|
|
312
|
+
return {
|
|
313
|
+
refresh,
|
|
314
|
+
status: () => ({ result: last, error: lastError }),
|
|
315
|
+
close: async () => {
|
|
316
|
+
if (closed)
|
|
317
|
+
return;
|
|
318
|
+
closed = true;
|
|
319
|
+
if (retryTimer)
|
|
320
|
+
clearTimeout(retryTimer);
|
|
321
|
+
if (subscriptionTimer)
|
|
322
|
+
clearTimeout(subscriptionTimer);
|
|
323
|
+
for (const entry of watchers.values())
|
|
324
|
+
entry.watcher.close();
|
|
325
|
+
watchers.clear();
|
|
326
|
+
await active;
|
|
327
|
+
},
|
|
328
|
+
};
|
|
329
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { type RolePlanAggregation, type RolePlanRequest } from './collab-role-plan.js';
|
|
2
|
+
import { type WorkPlanInspectionOptions, type WorkPlanReport } from './collab-work-plan.js';
|
|
3
|
+
import { type WorkExecutionOptions, type WorkExecutionReport, type WorkExecutionAdapter } from './collab-work-execution.js';
|
|
4
|
+
import { type RoleRepairDependencies, type RoleRepairPolicy } from './collab-role-repair.js';
|
|
5
|
+
export declare const COLLAB_ROLE_ORCHESTRATION_SCHEMA = "pappra/collab-role-orchestration/1";
|
|
6
|
+
export interface RoleOrchestrationLimits {
|
|
7
|
+
/** Maximum distinct evidence events (ticks) for this plan. */
|
|
8
|
+
readonly max_rounds: number;
|
|
9
|
+
/** Conservative maximum dispatch reservations across all ticks for this plan. */
|
|
10
|
+
readonly max_actions: number;
|
|
11
|
+
/** Maximum approved assignment attempt for rejected-node repair. */
|
|
12
|
+
readonly max_attempts: number;
|
|
13
|
+
}
|
|
14
|
+
export interface RoleOrchestrationRequest {
|
|
15
|
+
readonly role_plan: RolePlanRequest;
|
|
16
|
+
readonly execution: WorkExecutionOptions['request'];
|
|
17
|
+
/** Stable ID supplied by the triggering session event; repeated IDs never run twice. */
|
|
18
|
+
readonly event_id: string;
|
|
19
|
+
/** Task-local durable receipt file, e.g. `<task_root>/role-orchestration.json`. */
|
|
20
|
+
readonly journal_file: string;
|
|
21
|
+
readonly limits: RoleOrchestrationLimits;
|
|
22
|
+
/** Explicitly pre-authorized repair transitions; omitted requests retain legacy behavior. */
|
|
23
|
+
readonly repair_policy?: RoleRepairPolicy;
|
|
24
|
+
}
|
|
25
|
+
export type RoleOrchestrationStatus = 'dispatched' | 'waiting' | 'accepted' | 'repaired' | 'repair-required' | 'repair-exhausted' | 'blocked' | 'reconciliation-required' | 'budget-exhausted' | 'duplicate';
|
|
26
|
+
export interface RoleOrchestrationResult {
|
|
27
|
+
readonly schema: typeof COLLAB_ROLE_ORCHESTRATION_SCHEMA;
|
|
28
|
+
readonly status: RoleOrchestrationStatus;
|
|
29
|
+
readonly event_id: string;
|
|
30
|
+
readonly plan_id: string;
|
|
31
|
+
readonly round: number | null;
|
|
32
|
+
readonly reserved_actions: number;
|
|
33
|
+
readonly prior_outcome: string | null;
|
|
34
|
+
readonly aggregate: RolePlanAggregation | null;
|
|
35
|
+
readonly execution: WorkExecutionReport | null;
|
|
36
|
+
/** Returned for the root event driver to persist with its guarded-CAS request writer. */
|
|
37
|
+
readonly updatedRequest: RoleOrchestrationRequest | null;
|
|
38
|
+
readonly diagnostics: readonly string[];
|
|
39
|
+
}
|
|
40
|
+
export interface RoleOrchestrationDependencies {
|
|
41
|
+
readonly inspect?: (options: WorkPlanInspectionOptions) => Promise<WorkPlanReport>;
|
|
42
|
+
readonly execute?: (options: WorkExecutionOptions, deps?: {
|
|
43
|
+
readonly adapter?: WorkExecutionAdapter;
|
|
44
|
+
}) => Promise<WorkExecutionReport>;
|
|
45
|
+
readonly execution_adapter?: WorkExecutionAdapter;
|
|
46
|
+
readonly repair?: RoleRepairDependencies;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Advances one explicit session-event tick over the existing role-plan and WorkPlan state.
|
|
50
|
+
* The durable journal reserves the event and conservative action budget before execution;
|
|
51
|
+
* the task record, registered envelopes, and WorkPlan evaluator remain the only authority.
|
|
52
|
+
*/
|
|
53
|
+
export declare function tickRoleOrchestration(request: RoleOrchestrationRequest, deps?: RoleOrchestrationDependencies): Promise<RoleOrchestrationResult>;
|