@decibelsystems/tools 2.2.0-beta.0 → 3.1.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 +226 -0
- package/dist/agentCommands.d.ts +0 -1
- package/dist/agentCommands.d.ts.map +1 -1
- package/dist/agentCommands.js +19 -8
- package/dist/agentCommands.js.map +1 -1
- package/dist/agentPresence.d.ts +82 -0
- package/dist/agentPresence.d.ts.map +1 -1
- package/dist/agentPresence.js +194 -26
- package/dist/agentPresence.js.map +1 -1
- package/dist/agentic/index.d.ts +18 -8
- package/dist/agentic/index.d.ts.map +1 -1
- package/dist/agentic/index.js +18 -144
- package/dist/agentic/index.js.map +1 -1
- package/dist/agentic/types.d.ts +106 -0
- package/dist/agentic/types.d.ts.map +1 -1
- package/dist/agentic/types.js +77 -1
- package/dist/agentic/types.js.map +1 -1
- package/dist/architectAdrs.d.ts +27 -3
- package/dist/architectAdrs.d.ts.map +1 -1
- package/dist/architectAdrs.js +92 -4
- package/dist/architectAdrs.js.map +1 -1
- package/dist/artifacts.d.ts +60 -0
- package/dist/artifacts.d.ts.map +1 -0
- package/dist/artifacts.js +208 -0
- package/dist/artifacts.js.map +1 -0
- package/dist/asks.d.ts +93 -0
- package/dist/asks.d.ts.map +1 -0
- package/dist/asks.js +383 -0
- package/dist/asks.js.map +1 -0
- package/dist/chat.d.ts +47 -0
- package/dist/chat.d.ts.map +1 -0
- package/dist/chat.js +288 -0
- package/dist/chat.js.map +1 -0
- package/dist/client/transports.d.ts.map +1 -1
- package/dist/client/transports.js +13 -3
- package/dist/client/transports.js.map +1 -1
- package/dist/daemon.d.ts +33 -3
- package/dist/daemon.d.ts.map +1 -1
- package/dist/daemon.js +225 -52
- package/dist/daemon.js.map +1 -1
- package/dist/daemonConfig.d.ts +46 -0
- package/dist/daemonConfig.d.ts.map +1 -1
- package/dist/daemonConfig.js +57 -12
- package/dist/daemonConfig.js.map +1 -1
- package/dist/decibelPaths.d.ts.map +1 -1
- package/dist/decibelPaths.js +39 -6
- package/dist/decibelPaths.js.map +1 -1
- package/dist/domain/issue.d.ts +81 -0
- package/dist/domain/issue.d.ts.map +1 -0
- package/dist/domain/issue.js +106 -0
- package/dist/domain/issue.js.map +1 -0
- package/dist/domain/issueCodec.d.ts +39 -0
- package/dist/domain/issueCodec.d.ts.map +1 -0
- package/dist/domain/issueCodec.js +308 -0
- package/dist/domain/issueCodec.js.map +1 -0
- package/dist/domain/issueRepository.d.ts +124 -0
- package/dist/domain/issueRepository.d.ts.map +1 -0
- package/dist/domain/issueRepository.js +296 -0
- package/dist/domain/issueRepository.js.map +1 -0
- package/dist/domain/uid.d.ts +16 -0
- package/dist/domain/uid.d.ts.map +1 -0
- package/dist/domain/uid.js +43 -0
- package/dist/domain/uid.js.map +1 -0
- package/dist/facades/definitions.d.ts +0 -1
- package/dist/facades/definitions.d.ts.map +1 -1
- package/dist/facades/definitions.js +140 -116
- package/dist/facades/definitions.js.map +1 -1
- package/dist/facades/index.d.ts +2 -1
- package/dist/facades/index.d.ts.map +1 -1
- package/dist/facades/index.js +1 -1
- package/dist/facades/index.js.map +1 -1
- package/dist/facades/types.d.ts +17 -0
- package/dist/facades/types.d.ts.map +1 -1
- package/dist/hq/defineAgent.d.ts.map +1 -1
- package/dist/hq/defineAgent.js +7 -11
- package/dist/hq/defineAgent.js.map +1 -1
- package/dist/httpArgs.d.ts +11 -0
- package/dist/httpArgs.d.ts.map +1 -0
- package/dist/httpArgs.js +39 -0
- package/dist/httpArgs.js.map +1 -0
- package/dist/httpServer.d.ts +1 -12
- package/dist/httpServer.d.ts.map +1 -1
- package/dist/httpServer.js +298 -144
- package/dist/httpServer.js.map +1 -1
- package/dist/identity.d.ts +123 -0
- package/dist/identity.d.ts.map +1 -0
- package/dist/identity.js +312 -0
- package/dist/identity.js.map +1 -0
- package/dist/kernel.d.ts +46 -1
- package/dist/kernel.d.ts.map +1 -1
- package/dist/kernel.js +334 -26
- package/dist/kernel.js.map +1 -1
- package/dist/killSwitch.d.ts +117 -0
- package/dist/killSwitch.d.ts.map +1 -0
- package/dist/killSwitch.js +161 -0
- package/dist/killSwitch.js.map +1 -0
- package/dist/lib/agent-services/assumptions.d.ts.map +1 -1
- package/dist/lib/agent-services/assumptions.js +8 -2
- package/dist/lib/agent-services/assumptions.js.map +1 -1
- package/dist/lib/atomicWrite.d.ts +49 -0
- package/dist/lib/atomicWrite.d.ts.map +1 -0
- package/dist/lib/atomicWrite.js +157 -0
- package/dist/lib/atomicWrite.js.map +1 -0
- package/dist/lib/envelope.d.ts +31 -0
- package/dist/lib/envelope.d.ts.map +1 -0
- package/dist/lib/envelope.js +80 -0
- package/dist/lib/envelope.js.map +1 -0
- package/dist/lib/fileLock.d.ts +38 -0
- package/dist/lib/fileLock.d.ts.map +1 -0
- package/dist/lib/fileLock.js +124 -0
- package/dist/lib/fileLock.js.map +1 -0
- package/dist/lib/recordIdAllocator.d.ts +102 -0
- package/dist/lib/recordIdAllocator.d.ts.map +1 -0
- package/dist/lib/recordIdAllocator.js +179 -0
- package/dist/lib/recordIdAllocator.js.map +1 -0
- package/dist/orgId.d.ts +23 -0
- package/dist/orgId.d.ts.map +1 -0
- package/dist/orgId.js +73 -0
- package/dist/orgId.js.map +1 -0
- package/dist/pairCli.d.ts +2 -0
- package/dist/pairCli.d.ts.map +1 -0
- package/dist/pairCli.js +97 -0
- package/dist/pairCli.js.map +1 -0
- package/dist/peerBroker.d.ts +40 -0
- package/dist/peerBroker.d.ts.map +1 -0
- package/dist/peerBroker.js +70 -0
- package/dist/peerBroker.js.map +1 -0
- package/dist/peerBrokerSupervisor.d.ts +21 -0
- package/dist/peerBrokerSupervisor.d.ts.map +1 -0
- package/dist/peerBrokerSupervisor.js +131 -0
- package/dist/peerBrokerSupervisor.js.map +1 -0
- package/dist/projectPaths.d.ts.map +1 -1
- package/dist/projectPaths.js +1 -19
- package/dist/projectPaths.js.map +1 -1
- package/dist/projectRegistry.d.ts +22 -0
- package/dist/projectRegistry.d.ts.map +1 -1
- package/dist/projectRegistry.js +69 -2
- package/dist/projectRegistry.js.map +1 -1
- package/dist/pty/boundary.d.ts +39 -0
- package/dist/pty/boundary.d.ts.map +1 -0
- package/dist/pty/boundary.js +100 -0
- package/dist/pty/boundary.js.map +1 -0
- package/dist/pty/errors.d.ts +6 -0
- package/dist/pty/errors.d.ts.map +1 -0
- package/dist/pty/errors.js +12 -0
- package/dist/pty/errors.js.map +1 -0
- package/dist/pty/fleet.d.ts +159 -0
- package/dist/pty/fleet.d.ts.map +1 -0
- package/dist/pty/fleet.js +543 -0
- package/dist/pty/fleet.js.map +1 -0
- package/dist/pty/launchProfile.d.ts +45 -0
- package/dist/pty/launchProfile.d.ts.map +1 -0
- package/dist/pty/launchProfile.js +149 -0
- package/dist/pty/launchProfile.js.map +1 -0
- package/dist/pty/openTerminal.d.ts +48 -0
- package/dist/pty/openTerminal.d.ts.map +1 -0
- package/dist/pty/openTerminal.js +185 -0
- package/dist/pty/openTerminal.js.map +1 -0
- package/dist/pty/pairDialog.d.ts +16 -0
- package/dist/pty/pairDialog.d.ts.map +1 -0
- package/dist/pty/pairDialog.js +121 -0
- package/dist/pty/pairDialog.js.map +1 -0
- package/dist/pty/pairing.d.ts +86 -0
- package/dist/pty/pairing.d.ts.map +1 -0
- package/dist/pty/pairing.js +351 -0
- package/dist/pty/pairing.js.map +1 -0
- package/dist/pty/routes.d.ts +49 -0
- package/dist/pty/routes.d.ts.map +1 -0
- package/dist/pty/routes.js +624 -0
- package/dist/pty/routes.js.map +1 -0
- package/dist/pty/sessions.d.ts +167 -0
- package/dist/pty/sessions.d.ts.map +1 -0
- package/dist/pty/sessions.js +513 -0
- package/dist/pty/sessions.js.map +1 -0
- package/dist/pty/tmux.d.ts +109 -0
- package/dist/pty/tmux.d.ts.map +1 -0
- package/dist/pty/tmux.js +271 -0
- package/dist/pty/tmux.js.map +1 -0
- package/dist/roster.d.ts +37 -0
- package/dist/roster.d.ts.map +1 -0
- package/dist/roster.js +126 -0
- package/dist/roster.js.map +1 -0
- package/dist/runtime/circuitBreaker.d.ts +95 -0
- package/dist/runtime/circuitBreaker.d.ts.map +1 -0
- package/dist/runtime/circuitBreaker.js +178 -0
- package/dist/runtime/circuitBreaker.js.map +1 -0
- package/dist/runtime/daemonMeta.d.ts +53 -0
- package/dist/runtime/daemonMeta.d.ts.map +1 -0
- package/dist/runtime/daemonMeta.js +121 -0
- package/dist/runtime/daemonMeta.js.map +1 -0
- package/dist/runtime/ensureRuntime.d.ts +103 -0
- package/dist/runtime/ensureRuntime.d.ts.map +1 -0
- package/dist/runtime/ensureRuntime.js +276 -0
- package/dist/runtime/ensureRuntime.js.map +1 -0
- package/dist/runtime/extensions.d.ts +57 -0
- package/dist/runtime/extensions.d.ts.map +1 -0
- package/dist/runtime/extensions.js +216 -0
- package/dist/runtime/extensions.js.map +1 -0
- package/dist/runtime/localHttp.d.ts +24 -0
- package/dist/runtime/localHttp.d.ts.map +1 -0
- package/dist/runtime/localHttp.js +84 -0
- package/dist/runtime/localHttp.js.map +1 -0
- package/dist/runtime/projectResolution.d.ts +29 -0
- package/dist/runtime/projectResolution.d.ts.map +1 -0
- package/dist/runtime/projectResolution.js +67 -0
- package/dist/runtime/projectResolution.js.map +1 -0
- package/dist/runtime/protocol.d.ts +62 -0
- package/dist/runtime/protocol.d.ts.map +1 -0
- package/dist/runtime/protocol.js +74 -0
- package/dist/runtime/protocol.js.map +1 -0
- package/dist/sentinelIssues.d.ts +42 -2
- package/dist/sentinelIssues.d.ts.map +1 -1
- package/dist/sentinelIssues.js +167 -65
- package/dist/sentinelIssues.js.map +1 -1
- package/dist/server.js +123 -15
- package/dist/server.js.map +1 -1
- package/dist/toolConfig.d.ts.map +1 -1
- package/dist/toolConfig.js +12 -0
- package/dist/toolConfig.js.map +1 -1
- package/dist/tools/agentic/agentQueue.d.ts.map +1 -1
- package/dist/tools/agentic/agentQueue.js +4 -0
- package/dist/tools/agentic/agentQueue.js.map +1 -1
- package/dist/tools/agentic/index.d.ts.map +1 -1
- package/dist/tools/agentic/index.js +28 -2
- package/dist/tools/agentic/index.js.map +1 -1
- package/dist/tools/architect/index.d.ts +1 -0
- package/dist/tools/architect/index.d.ts.map +1 -1
- package/dist/tools/architect/index.js +52 -3
- package/dist/tools/architect/index.js.map +1 -1
- package/dist/tools/architect.d.ts.map +1 -1
- package/dist/tools/architect.js +59 -8
- package/dist/tools/architect.js.map +1 -1
- package/dist/tools/artifacts.d.ts +5 -0
- package/dist/tools/artifacts.d.ts.map +1 -0
- package/dist/tools/artifacts.js +89 -0
- package/dist/tools/artifacts.js.map +1 -0
- package/dist/tools/asks.d.ts +6 -0
- package/dist/tools/asks.d.ts.map +1 -0
- package/dist/tools/asks.js +104 -0
- package/dist/tools/asks.js.map +1 -0
- package/dist/tools/context.d.ts +10 -5
- package/dist/tools/context.d.ts.map +1 -1
- package/dist/tools/context.js +45 -18
- package/dist/tools/context.js.map +1 -1
- package/dist/tools/coordinator/coordinator.d.ts.map +1 -1
- package/dist/tools/coordinator/coordinator.js +48 -19
- package/dist/tools/coordinator/coordinator.js.map +1 -1
- package/dist/tools/coordinator/index.d.ts +1 -0
- package/dist/tools/coordinator/index.d.ts.map +1 -1
- package/dist/tools/coordinator/index.js +42 -2
- package/dist/tools/coordinator/index.js.map +1 -1
- package/dist/tools/crit.d.ts +2 -1
- package/dist/tools/crit.d.ts.map +1 -1
- package/dist/tools/crit.js +19 -4
- package/dist/tools/crit.js.map +1 -1
- package/dist/tools/data-inspector.d.ts +1 -1
- package/dist/tools/data-inspector.d.ts.map +1 -1
- package/dist/tools/data-inspector.js +1 -1
- package/dist/tools/data-inspector.js.map +1 -1
- package/dist/tools/designer/index.d.ts.map +1 -1
- package/dist/tools/designer/index.js +4 -3
- package/dist/tools/designer/index.js.map +1 -1
- package/dist/tools/designer/lateral-tools.d.ts.map +1 -1
- package/dist/tools/designer/lateral-tools.js +4 -2
- package/dist/tools/designer/lateral-tools.js.map +1 -1
- package/dist/tools/designer.d.ts +2 -1
- package/dist/tools/designer.d.ts.map +1 -1
- package/dist/tools/designer.js +24 -12
- package/dist/tools/designer.js.map +1 -1
- package/dist/tools/dojo/index.d.ts.map +1 -1
- package/dist/tools/dojo/index.js +52 -3
- package/dist/tools/dojo/index.js.map +1 -1
- package/dist/tools/dojo.d.ts +65 -3
- package/dist/tools/dojo.d.ts.map +1 -1
- package/dist/tools/dojo.js +148 -23
- package/dist/tools/dojo.js.map +1 -1
- package/dist/tools/friction.d.ts.map +1 -1
- package/dist/tools/friction.js +16 -6
- package/dist/tools/friction.js.map +1 -1
- package/dist/tools/guardian.d.ts +7 -0
- package/dist/tools/guardian.d.ts.map +1 -1
- package/dist/tools/guardian.js +152 -11
- package/dist/tools/guardian.js.map +1 -1
- package/dist/tools/hygiene/oracle-hygiene.d.ts.map +1 -1
- package/dist/tools/hygiene/oracle-hygiene.js +8 -2
- package/dist/tools/hygiene/oracle-hygiene.js.map +1 -1
- package/dist/tools/index.d.ts.map +1 -1
- package/dist/tools/index.js +33 -18
- package/dist/tools/index.js.map +1 -1
- package/dist/tools/killswitch/index.d.ts +13 -0
- package/dist/tools/killswitch/index.d.ts.map +1 -0
- package/dist/tools/killswitch/index.js +138 -0
- package/dist/tools/killswitch/index.js.map +1 -0
- package/dist/tools/learnings.d.ts +2 -1
- package/dist/tools/learnings.d.ts.map +1 -1
- package/dist/tools/learnings.js +31 -15
- package/dist/tools/learnings.js.map +1 -1
- package/dist/tools/oracle/index.d.ts.map +1 -1
- package/dist/tools/oracle/index.js +8 -0
- package/dist/tools/oracle/index.js.map +1 -1
- package/dist/tools/oracle.d.ts +7 -0
- package/dist/tools/oracle.d.ts.map +1 -1
- package/dist/tools/oracle.js +41 -12
- package/dist/tools/oracle.js.map +1 -1
- package/dist/tools/policy.d.ts +2 -1
- package/dist/tools/policy.d.ts.map +1 -1
- package/dist/tools/policy.js +28 -9
- package/dist/tools/policy.js.map +1 -1
- package/dist/tools/postoffice/index.d.ts +10 -0
- package/dist/tools/postoffice/index.d.ts.map +1 -0
- package/dist/tools/postoffice/index.js +218 -0
- package/dist/tools/postoffice/index.js.map +1 -0
- package/dist/tools/postoffice.d.ts +78 -0
- package/dist/tools/postoffice.d.ts.map +1 -0
- package/dist/tools/postoffice.js +199 -0
- package/dist/tools/postoffice.js.map +1 -0
- package/dist/tools/provenance.d.ts +18 -2
- package/dist/tools/provenance.d.ts.map +1 -1
- package/dist/tools/provenance.js +68 -40
- package/dist/tools/provenance.js.map +1 -1
- package/dist/tools/registry/index.d.ts.map +1 -1
- package/dist/tools/registry/index.js +24 -0
- package/dist/tools/registry/index.js.map +1 -1
- package/dist/tools/sentinel/index.d.ts +1 -0
- package/dist/tools/sentinel/index.d.ts.map +1 -1
- package/dist/tools/sentinel/index.js +142 -6
- package/dist/tools/sentinel/index.js.map +1 -1
- package/dist/tools/sentinel.d.ts +152 -1
- package/dist/tools/sentinel.d.ts.map +1 -1
- package/dist/tools/sentinel.js +609 -328
- package/dist/tools/sentinel.js.map +1 -1
- package/dist/tools/shared/index.d.ts +2 -1
- package/dist/tools/shared/index.d.ts.map +1 -1
- package/dist/tools/shared/index.js +2 -1
- package/dist/tools/shared/index.js.map +1 -1
- package/dist/tools/shared/response.d.ts +24 -0
- package/dist/tools/shared/response.d.ts.map +1 -1
- package/dist/tools/shared/response.js +61 -7
- package/dist/tools/shared/response.js.map +1 -1
- package/dist/tools/shared/runTracker.d.ts +2 -0
- package/dist/tools/shared/runTracker.d.ts.map +1 -1
- package/dist/tools/shared/runTracker.js +38 -0
- package/dist/tools/shared/runTracker.js.map +1 -1
- package/dist/tools/shared/storeRead.d.ts +153 -0
- package/dist/tools/shared/storeRead.d.ts.map +1 -0
- package/dist/tools/shared/storeRead.js +242 -0
- package/dist/tools/shared/storeRead.js.map +1 -0
- package/dist/tools/swarm.d.ts +3 -1
- package/dist/tools/swarm.d.ts.map +1 -1
- package/dist/tools/swarm.js +6 -12
- package/dist/tools/swarm.js.map +1 -1
- package/dist/tools/testSpec.d.ts +3 -2
- package/dist/tools/testSpec.d.ts.map +1 -1
- package/dist/tools/testSpec.js +51 -14
- package/dist/tools/testSpec.js.map +1 -1
- package/dist/tools/vector.d.ts +33 -1
- package/dist/tools/vector.d.ts.map +1 -1
- package/dist/tools/vector.js +31 -5
- package/dist/tools/vector.js.map +1 -1
- package/dist/tools/voice/index.d.ts +1 -0
- package/dist/tools/voice/index.d.ts.map +1 -1
- package/dist/tools/voice/index.js +33 -2
- package/dist/tools/voice/index.js.map +1 -1
- package/dist/tools/voice.d.ts +48 -1
- package/dist/tools/voice.d.ts.map +1 -1
- package/dist/tools/voice.js +63 -3
- package/dist/tools/voice.js.map +1 -1
- package/dist/tools/zoom/index.d.ts +15 -0
- package/dist/tools/zoom/index.d.ts.map +1 -0
- package/dist/tools/zoom/index.js +174 -0
- package/dist/tools/zoom/index.js.map +1 -0
- package/dist/tools/zoom.d.ts +234 -0
- package/dist/tools/zoom.d.ts.map +1 -0
- package/dist/tools/zoom.js +627 -0
- package/dist/tools/zoom.js.map +1 -0
- package/dist/transports/bridge.d.ts +1 -1
- package/dist/transports/bridge.d.ts.map +1 -1
- package/dist/transports/bridge.js +4 -1
- package/dist/transports/bridge.js.map +1 -1
- package/dist/transports/http.d.ts +1 -1
- package/dist/transports/http.d.ts.map +1 -1
- package/dist/transports/http.js +3 -1
- package/dist/transports/http.js.map +1 -1
- package/dist/transports/index.d.ts +1 -0
- package/dist/transports/index.d.ts.map +1 -1
- package/dist/transports/index.js +1 -0
- package/dist/transports/index.js.map +1 -1
- package/dist/transports/mcp.d.ts +1 -1
- package/dist/transports/mcp.d.ts.map +1 -1
- package/dist/transports/mcp.js +9 -4
- package/dist/transports/mcp.js.map +1 -1
- package/dist/transports/stdio.d.ts +1 -1
- package/dist/transports/stdio.d.ts.map +1 -1
- package/dist/transports/stdio.js +2 -0
- package/dist/transports/stdio.js.map +1 -1
- package/dist/transports/thinStdio.d.ts +22 -0
- package/dist/transports/thinStdio.d.ts.map +1 -0
- package/dist/transports/thinStdio.js +218 -0
- package/dist/transports/thinStdio.js.map +1 -0
- package/dist/transports/types.d.ts +8 -1
- package/dist/transports/types.d.ts.map +1 -1
- package/dist/types/agent-services.d.ts +7 -0
- package/dist/types/agent-services.d.ts.map +1 -1
- package/dist/version.d.ts +5 -0
- package/dist/version.d.ts.map +1 -0
- package/dist/version.js +23 -0
- package/dist/version.js.map +1 -0
- package/package.json +20 -4
- package/templates/AGENT.md +3 -0
- package/templates/CLAUDE-decibel.md +11 -0
- package/templates/com.decibel.daemon.plist +6 -1
- package/dist/tools/deck.d.ts +0 -11
- package/dist/tools/deck.d.ts.map +0 -1
- package/dist/tools/deck.js +0 -1656
- package/dist/tools/deck.js.map +0 -1
- package/dist/tools/mother.d.ts +0 -11
- package/dist/tools/mother.d.ts.map +0 -1
- package/dist/tools/mother.js +0 -503
- package/dist/tools/mother.js.map +0 -1
- package/dist/tools/registry.d.ts +0 -3
- package/dist/tools/registry.d.ts.map +0 -1
- package/dist/tools/registry.js +0 -189
- package/dist/tools/registry.js.map +0 -1
- package/dist/tools/senken.d.ts +0 -11
- package/dist/tools/senken.d.ts.map +0 -1
- package/dist/tools/senken.js +0 -482
- package/dist/tools/senken.js.map +0 -1
- package/dist/tools/terminal.d.ts +0 -17
- package/dist/tools/terminal.d.ts.map +0 -1
- package/dist/tools/terminal.js +0 -968
- package/dist/tools/terminal.js.map +0 -1
- package/dist/tools/toolsIndex.d.ts +0 -5
- package/dist/tools/toolsIndex.d.ts.map +0 -1
- package/dist/tools/toolsIndex.js +0 -37
- package/dist/tools/toolsIndex.js.map +0 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fileLock.d.ts","sourceRoot":"","sources":["../../src/lib/fileLock.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAKH,MAAM,WAAW,eAAe;IAC9B,yCAAyC;IACzC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,wEAAwE;IACxE,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,0CAA0C;IAC1C,eAAe,CAAC,EAAE,MAAM,CAAC;CAC1B;AAMD,qEAAqE;AACrE,qBAAa,oBAAqB,SAAQ,KAAK;gBACjC,QAAQ,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM;CAQjE;AA8DD;;;;;GAKG;AACH,wBAAsB,YAAY,CAAC,CAAC,EAClC,QAAQ,EAAE,MAAM,EAChB,EAAE,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,EACpB,OAAO,GAAE,eAAoB,GAC5B,OAAO,CAAC,CAAC,CAAC,CAyBZ"}
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cross-process advisory file lock.
|
|
3
|
+
*
|
|
4
|
+
* Decibel's stores are plain files in `.decibel/`, written concurrently by
|
|
5
|
+
* every MCP client, CLI invocation, hook, and migration script on the machine.
|
|
6
|
+
* An in-process mutex is useless here — the contending writers are separate
|
|
7
|
+
* processes, often separate installs.
|
|
8
|
+
*
|
|
9
|
+
* Acquisition is `open(path, 'wx')`: O_EXCL, which either creates the file
|
|
10
|
+
* atomically or fails with EEXIST. There is no read-then-write window for a
|
|
11
|
+
* second caller to slip through, which is the property that makes this safe
|
|
12
|
+
* where a "does a lock exist?" check would not be.
|
|
13
|
+
*
|
|
14
|
+
* The lock is advisory: it only protects callers that take it. That is why the
|
|
15
|
+
* operations it guards should *also* be individually safe (e.g. creating a file
|
|
16
|
+
* with O_EXCL so a colliding write fails loudly). Old clients, one-off scripts,
|
|
17
|
+
* and migrations are part of the threat model and will not take the lock.
|
|
18
|
+
*/
|
|
19
|
+
import { openSync, closeSync, writeSync, unlinkSync, statSync, mkdirSync } from 'fs';
|
|
20
|
+
import { dirname } from 'path';
|
|
21
|
+
const DEFAULT_TIMEOUT_MS = 10_000;
|
|
22
|
+
const DEFAULT_STALE_MS = 30_000;
|
|
23
|
+
const DEFAULT_RETRY_INTERVAL_MS = 25;
|
|
24
|
+
/** Thrown when the lock could not be acquired before the timeout. */
|
|
25
|
+
export class FileLockTimeoutError extends Error {
|
|
26
|
+
constructor(lockPath, timeoutMs, holder) {
|
|
27
|
+
super(`Timed out after ${timeoutMs}ms acquiring lock ${lockPath}` +
|
|
28
|
+
(holder ? ` (held by ${holder})` : '') +
|
|
29
|
+
`. If no Decibel process is running, remove the lock file and retry.`);
|
|
30
|
+
this.name = 'FileLockTimeoutError';
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
function readHolder(lockPath) {
|
|
34
|
+
try {
|
|
35
|
+
const raw = statSync(lockPath);
|
|
36
|
+
return `pid unknown, age ${Math.round((Date.now() - raw.mtimeMs) / 1000)}s`;
|
|
37
|
+
}
|
|
38
|
+
catch {
|
|
39
|
+
return undefined;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
/** One acquisition attempt. Returns true on success. */
|
|
43
|
+
function tryAcquire(lockPath, staleMs) {
|
|
44
|
+
mkdirSync(dirname(lockPath), { recursive: true });
|
|
45
|
+
try {
|
|
46
|
+
const fd = openSync(lockPath, 'wx');
|
|
47
|
+
try {
|
|
48
|
+
writeSync(fd, JSON.stringify({ pid: process.pid, at: new Date().toISOString() }));
|
|
49
|
+
}
|
|
50
|
+
finally {
|
|
51
|
+
closeSync(fd);
|
|
52
|
+
}
|
|
53
|
+
return true;
|
|
54
|
+
}
|
|
55
|
+
catch (err) {
|
|
56
|
+
if (err.code !== 'EEXIST')
|
|
57
|
+
throw err;
|
|
58
|
+
}
|
|
59
|
+
// Held. Abandoned by a crashed holder?
|
|
60
|
+
try {
|
|
61
|
+
const age = Date.now() - statSync(lockPath).mtimeMs;
|
|
62
|
+
if (age > staleMs) {
|
|
63
|
+
unlinkSync(lockPath);
|
|
64
|
+
// One retry. If we lose this one too, another waiter beat us to the
|
|
65
|
+
// steal — fine, they hold it legitimately and we keep waiting.
|
|
66
|
+
try {
|
|
67
|
+
const fd = openSync(lockPath, 'wx');
|
|
68
|
+
try {
|
|
69
|
+
writeSync(fd, JSON.stringify({ pid: process.pid, at: new Date().toISOString() }));
|
|
70
|
+
}
|
|
71
|
+
finally {
|
|
72
|
+
closeSync(fd);
|
|
73
|
+
}
|
|
74
|
+
return true;
|
|
75
|
+
}
|
|
76
|
+
catch {
|
|
77
|
+
return false;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
catch {
|
|
82
|
+
// Lock vanished between EEXIST and stat — the holder released it. The next
|
|
83
|
+
// loop iteration will acquire normally.
|
|
84
|
+
}
|
|
85
|
+
return false;
|
|
86
|
+
}
|
|
87
|
+
function release(lockPath) {
|
|
88
|
+
try {
|
|
89
|
+
unlinkSync(lockPath);
|
|
90
|
+
}
|
|
91
|
+
catch {
|
|
92
|
+
// Already gone (released elsewhere, or stolen as stale). Nothing to do.
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Run `fn` while holding an exclusive cross-process lock.
|
|
97
|
+
*
|
|
98
|
+
* The lock is always released, including when `fn` throws — a failed write must
|
|
99
|
+
* not wedge every other writer on the machine.
|
|
100
|
+
*/
|
|
101
|
+
export async function withFileLock(lockPath, fn, options = {}) {
|
|
102
|
+
const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
103
|
+
const staleMs = options.staleMs ?? DEFAULT_STALE_MS;
|
|
104
|
+
const retryIntervalMs = options.retryIntervalMs ?? DEFAULT_RETRY_INTERVAL_MS;
|
|
105
|
+
const deadline = Date.now() + timeoutMs;
|
|
106
|
+
let acquired = false;
|
|
107
|
+
while (Date.now() < deadline) {
|
|
108
|
+
if (tryAcquire(lockPath, staleMs)) {
|
|
109
|
+
acquired = true;
|
|
110
|
+
break;
|
|
111
|
+
}
|
|
112
|
+
await new Promise((r) => setTimeout(r, retryIntervalMs));
|
|
113
|
+
}
|
|
114
|
+
if (!acquired) {
|
|
115
|
+
throw new FileLockTimeoutError(lockPath, timeoutMs, readHolder(lockPath));
|
|
116
|
+
}
|
|
117
|
+
try {
|
|
118
|
+
return await fn();
|
|
119
|
+
}
|
|
120
|
+
finally {
|
|
121
|
+
release(lockPath);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
//# sourceMappingURL=fileLock.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fileLock.js","sourceRoot":"","sources":["../../src/lib/fileLock.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Serialized ISS-NNNN and EPIC-NNNN allocation.
|
|
3
|
+
*
|
|
4
|
+
* The defect this closes: `create_issue` picked the next id by scanning the
|
|
5
|
+
* issues directory for the current maximum, then wrote the file many awaits
|
|
6
|
+
* later. Two processes could read the same maximum and both write ISS-0141.
|
|
7
|
+
* Four duplicate-id groups were produced by exactly this and were found on disk
|
|
8
|
+
* (ISS-0015, ISS-0028, ISS-0054, ISS-0112), reported independently by the
|
|
9
|
+
* `machina` peer and reproduced locally. THIS repo's four were cleared: as of
|
|
10
|
+
* 2026-09-19 it holds 200 issues and 38 epics with ZERO duplicate frontmatter
|
|
11
|
+
* ids. ISS-0136 remains OPEN, because it is portfolio-wide and 61 groups still
|
|
12
|
+
* sit in five other projects (frontend_v0.2, senken-trading-agent, machina,
|
|
13
|
+
* decibel-studio, decibel-tools-mobile). Do not read "clean" here as "clean
|
|
14
|
+
* everywhere". The history is kept because it is the evidence
|
|
15
|
+
* this lock exists, not because the state persists — stated in the past tense
|
|
16
|
+
* deliberately, since the present-tense version outlived the fact and was read
|
|
17
|
+
* as current by an outside reader, who repeated it in a document.
|
|
18
|
+
*
|
|
19
|
+
* Verify by parsing frontmatter, not by grepping. `grep "^id: ISS-"` over this
|
|
20
|
+
* store counts documents that QUOTE an id — a report about malformed
|
|
21
|
+
* frontmatter contains the line it is reporting — and that false positive has
|
|
22
|
+
* already cost one confidently wrong correction.
|
|
23
|
+
*
|
|
24
|
+
* Two independent defences, because a lock only protects callers that take it:
|
|
25
|
+
*
|
|
26
|
+
* 1. The lock spans **allocation through successful write**, not just the id
|
|
27
|
+
* calculation. Serializing only the scan leaves the same race — both
|
|
28
|
+
* processes still compute 141 before either file lands.
|
|
29
|
+
*
|
|
30
|
+
* 2. The write itself is exclusive. If a caller bypasses the lock entirely —
|
|
31
|
+
* an old client, a migration script, a future mistake — a colliding write
|
|
32
|
+
* fails loudly with EEXIST instead of silently overwriting a real issue.
|
|
33
|
+
* It is also atomic (ISS-0195): the record is published by link(2) from a
|
|
34
|
+
* complete, fsynced temp, so a SIGKILL mid-write can no longer leave a
|
|
35
|
+
* 0-byte record under a real id, which plain O_EXCL could.
|
|
36
|
+
*
|
|
37
|
+
* Both `createIssue` implementations (src/tools/sentinel.ts and
|
|
38
|
+
* src/sentinelIssues.ts) allocate from a single numbering space and write
|
|
39
|
+
* different formats into the same directory, so they must share this path or
|
|
40
|
+
* they race against each other.
|
|
41
|
+
*/
|
|
42
|
+
/** The record kinds that carry an allocated NNNN. */
|
|
43
|
+
export type RecordPrefix = 'ISS' | 'EPIC';
|
|
44
|
+
export declare function formatRecordId(prefix: RecordPrefix, num: number): string;
|
|
45
|
+
export declare function formatIssueId(num: number): string;
|
|
46
|
+
/**
|
|
47
|
+
* Highest PREFIX-NNNN currently present, by filename prefix or frontmatter id.
|
|
48
|
+
*
|
|
49
|
+
* Both sources are checked because the two record formats disagree: PREFIX-NNNN
|
|
50
|
+
* files carry the id in the name, while timestamp-slug records carry it only in
|
|
51
|
+
* frontmatter. Scanning one source alone under-counts and hands out an id that
|
|
52
|
+
* is already taken.
|
|
53
|
+
*/
|
|
54
|
+
export declare function scanMaxRecordNumber(dir: string, prefix: RecordPrefix): Promise<number>;
|
|
55
|
+
export declare function scanMaxIssueNumber(issuesDir: string): Promise<number>;
|
|
56
|
+
export interface AllocatedRecord {
|
|
57
|
+
id: string;
|
|
58
|
+
filename: string;
|
|
59
|
+
filePath: string;
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Allocate the next PREFIX-NNNN and write the record, with both held under one
|
|
63
|
+
* lock so no other process can allocate the same id in between.
|
|
64
|
+
*
|
|
65
|
+
* `buildContent` receives the allocated id because the id appears inside the
|
|
66
|
+
* record body (frontmatter `id:`), so content cannot be built before allocation.
|
|
67
|
+
*
|
|
68
|
+
* On the rare EEXIST — a bypassing writer took the id between our scan and our
|
|
69
|
+
* write — the allocation is retried rather than failed, up to `maxAttempts`.
|
|
70
|
+
* Retrying is correct here: the id is genuinely taken, and the next one up is a
|
|
71
|
+
* valid answer to the caller's request.
|
|
72
|
+
*/
|
|
73
|
+
export declare function allocateAndWriteRecord(dir: string, prefix: RecordPrefix, extension: 'md' | 'yml', slug: string, buildContent: (recordId: string) => string, options?: {
|
|
74
|
+
maxAttempts?: number;
|
|
75
|
+
timeoutMs?: number;
|
|
76
|
+
/** Called with the resolved path before each write attempt, to reject a
|
|
77
|
+
* path that escapes the project. Throwing aborts the allocation. */
|
|
78
|
+
validate?: (filePath: string) => void;
|
|
79
|
+
}): Promise<AllocatedRecord>;
|
|
80
|
+
/** ISS-NNNN allocation. See {@link allocateAndWriteRecord}. */
|
|
81
|
+
export declare function allocateAndWriteIssue(issuesDir: string, extension: 'md' | 'yml', slug: string, buildContent: (issueId: string) => string, options?: {
|
|
82
|
+
maxAttempts?: number;
|
|
83
|
+
timeoutMs?: number;
|
|
84
|
+
validate?: (filePath: string) => void;
|
|
85
|
+
}): Promise<AllocatedRecord>;
|
|
86
|
+
/**
|
|
87
|
+
* EPIC-NNNN allocation.
|
|
88
|
+
*
|
|
89
|
+
* Added because `logEpic` had the pre-lock defect verbatim: it read the
|
|
90
|
+
* directory for the current maximum, then wrote the file ~20 awaits later.
|
|
91
|
+
* Epics are the worse half of that race. An issue collision at least left two
|
|
92
|
+
* files and a visible duplicate id; the epic path wrote through
|
|
93
|
+
* `writeFileAtomic`, whose rename CLOBBERS, so the loser was not duplicated —
|
|
94
|
+
* it was gone, and its caller had already been told it was created. Found by
|
|
95
|
+
* the S5 concurrency sweep.
|
|
96
|
+
*/
|
|
97
|
+
export declare function allocateAndWriteEpic(epicsDir: string, slug: string, buildContent: (epicId: string) => string, options?: {
|
|
98
|
+
maxAttempts?: number;
|
|
99
|
+
timeoutMs?: number;
|
|
100
|
+
validate?: (filePath: string) => void;
|
|
101
|
+
}): Promise<AllocatedRecord>;
|
|
102
|
+
//# sourceMappingURL=recordIdAllocator.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"recordIdAllocator.d.ts","sourceRoot":"","sources":["../../src/lib/recordIdAllocator.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAwCG;AAeH,qDAAqD;AACrD,MAAM,MAAM,YAAY,GAAG,KAAK,GAAG,MAAM,CAAC;AAgB1C,wBAAgB,cAAc,CAAC,MAAM,EAAE,YAAY,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM,CAExE;AAED,wBAAgB,aAAa,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAEjD;AAED;;;;;;;GAOG;AACH,wBAAsB,mBAAmB,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,YAAY,GAAG,OAAO,CAAC,MAAM,CAAC,CAiC5F;AAED,wBAAsB,kBAAkB,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAE3E;AAED,MAAM,WAAW,eAAe;IAC9B,EAAE,EAAE,MAAM,CAAC;IACX,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED;;;;;;;;;;;GAWG;AACH,wBAAsB,sBAAsB,CAC1C,GAAG,EAAE,MAAM,EACX,MAAM,EAAE,YAAY,EACpB,SAAS,EAAE,IAAI,GAAG,KAAK,EACvB,IAAI,EAAE,MAAM,EACZ,YAAY,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,MAAM,EAC1C,OAAO,GAAE;IACP,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB;yEACqE;IACrE,QAAQ,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,IAAI,CAAC;CAClC,GACL,OAAO,CAAC,eAAe,CAAC,CAiC1B;AAED,+DAA+D;AAC/D,wBAAsB,qBAAqB,CACzC,SAAS,EAAE,MAAM,EACjB,SAAS,EAAE,IAAI,GAAG,KAAK,EACvB,IAAI,EAAE,MAAM,EACZ,YAAY,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,MAAM,EACzC,OAAO,GAAE;IAAE,WAAW,CAAC,EAAE,MAAM,CAAC;IAAC,SAAS,CAAC,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,IAAI,CAAA;CAAO,GAChG,OAAO,CAAC,eAAe,CAAC,CAE1B;AAED;;;;;;;;;;GAUG;AACH,wBAAsB,oBAAoB,CACxC,QAAQ,EAAE,MAAM,EAChB,IAAI,EAAE,MAAM,EACZ,YAAY,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,MAAM,EACxC,OAAO,GAAE;IAAE,WAAW,CAAC,EAAE,MAAM,CAAC;IAAC,SAAS,CAAC,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,IAAI,CAAA;CAAO,GAChG,OAAO,CAAC,eAAe,CAAC,CAE1B"}
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Serialized ISS-NNNN and EPIC-NNNN allocation.
|
|
3
|
+
*
|
|
4
|
+
* The defect this closes: `create_issue` picked the next id by scanning the
|
|
5
|
+
* issues directory for the current maximum, then wrote the file many awaits
|
|
6
|
+
* later. Two processes could read the same maximum and both write ISS-0141.
|
|
7
|
+
* Four duplicate-id groups were produced by exactly this and were found on disk
|
|
8
|
+
* (ISS-0015, ISS-0028, ISS-0054, ISS-0112), reported independently by the
|
|
9
|
+
* `machina` peer and reproduced locally. THIS repo's four were cleared: as of
|
|
10
|
+
* 2026-09-19 it holds 200 issues and 38 epics with ZERO duplicate frontmatter
|
|
11
|
+
* ids. ISS-0136 remains OPEN, because it is portfolio-wide and 61 groups still
|
|
12
|
+
* sit in five other projects (frontend_v0.2, senken-trading-agent, machina,
|
|
13
|
+
* decibel-studio, decibel-tools-mobile). Do not read "clean" here as "clean
|
|
14
|
+
* everywhere". The history is kept because it is the evidence
|
|
15
|
+
* this lock exists, not because the state persists — stated in the past tense
|
|
16
|
+
* deliberately, since the present-tense version outlived the fact and was read
|
|
17
|
+
* as current by an outside reader, who repeated it in a document.
|
|
18
|
+
*
|
|
19
|
+
* Verify by parsing frontmatter, not by grepping. `grep "^id: ISS-"` over this
|
|
20
|
+
* store counts documents that QUOTE an id — a report about malformed
|
|
21
|
+
* frontmatter contains the line it is reporting — and that false positive has
|
|
22
|
+
* already cost one confidently wrong correction.
|
|
23
|
+
*
|
|
24
|
+
* Two independent defences, because a lock only protects callers that take it:
|
|
25
|
+
*
|
|
26
|
+
* 1. The lock spans **allocation through successful write**, not just the id
|
|
27
|
+
* calculation. Serializing only the scan leaves the same race — both
|
|
28
|
+
* processes still compute 141 before either file lands.
|
|
29
|
+
*
|
|
30
|
+
* 2. The write itself is exclusive. If a caller bypasses the lock entirely —
|
|
31
|
+
* an old client, a migration script, a future mistake — a colliding write
|
|
32
|
+
* fails loudly with EEXIST instead of silently overwriting a real issue.
|
|
33
|
+
* It is also atomic (ISS-0195): the record is published by link(2) from a
|
|
34
|
+
* complete, fsynced temp, so a SIGKILL mid-write can no longer leave a
|
|
35
|
+
* 0-byte record under a real id, which plain O_EXCL could.
|
|
36
|
+
*
|
|
37
|
+
* Both `createIssue` implementations (src/tools/sentinel.ts and
|
|
38
|
+
* src/sentinelIssues.ts) allocate from a single numbering space and write
|
|
39
|
+
* different formats into the same directory, so they must share this path or
|
|
40
|
+
* they race against each other.
|
|
41
|
+
*/
|
|
42
|
+
import { promises as fs } from 'fs';
|
|
43
|
+
import path from 'path';
|
|
44
|
+
import { withFileLock } from './fileLock.js';
|
|
45
|
+
import { createFileExclusive, sweepAllocationTemps } from './atomicWrite.js';
|
|
46
|
+
/**
|
|
47
|
+
* Lock file guarding id allocation, scoped per record directory.
|
|
48
|
+
*
|
|
49
|
+
* Issues and epics number independently and live in different directories, so
|
|
50
|
+
* one lock file name per directory is exactly one lock per numbering space.
|
|
51
|
+
*/
|
|
52
|
+
const LOCK_FILENAME = '.record-id.lock';
|
|
53
|
+
/** Records the allocator considers when scanning for the current maximum. */
|
|
54
|
+
function isRecordFile(filename) {
|
|
55
|
+
return /\.(md|yml|yaml)$/i.test(filename);
|
|
56
|
+
}
|
|
57
|
+
function frontmatterRegion(content) {
|
|
58
|
+
const delimited = /^---\n([\s\S]*?)\n---/.exec(content);
|
|
59
|
+
if (delimited)
|
|
60
|
+
return delimited[1];
|
|
61
|
+
// Bare-YAML records have no delimiters; the whole head is the region.
|
|
62
|
+
if (/^---/.test(content.trimStart()))
|
|
63
|
+
return null;
|
|
64
|
+
const headingIdx = content.split('\n').findIndex((l) => /^#{1,6}\s/.test(l));
|
|
65
|
+
return headingIdx > 0 ? content.split('\n').slice(0, headingIdx).join('\n') : content;
|
|
66
|
+
}
|
|
67
|
+
export function formatRecordId(prefix, num) {
|
|
68
|
+
return `${prefix}-${num.toString().padStart(4, '0')}`;
|
|
69
|
+
}
|
|
70
|
+
export function formatIssueId(num) {
|
|
71
|
+
return formatRecordId('ISS', num);
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Highest PREFIX-NNNN currently present, by filename prefix or frontmatter id.
|
|
75
|
+
*
|
|
76
|
+
* Both sources are checked because the two record formats disagree: PREFIX-NNNN
|
|
77
|
+
* files carry the id in the name, while timestamp-slug records carry it only in
|
|
78
|
+
* frontmatter. Scanning one source alone under-counts and hands out an id that
|
|
79
|
+
* is already taken.
|
|
80
|
+
*/
|
|
81
|
+
export async function scanMaxRecordNumber(dir, prefix) {
|
|
82
|
+
let max = 0;
|
|
83
|
+
let files;
|
|
84
|
+
try {
|
|
85
|
+
files = await fs.readdir(dir);
|
|
86
|
+
}
|
|
87
|
+
catch {
|
|
88
|
+
return 0; // Directory does not exist yet.
|
|
89
|
+
}
|
|
90
|
+
const fromName = new RegExp(`^${prefix}-(\\d+)`, 'i');
|
|
91
|
+
const fromId = new RegExp(`^${prefix}-(\\d+)$`, 'i');
|
|
92
|
+
for (const file of files) {
|
|
93
|
+
const prefixMatch = file.match(fromName);
|
|
94
|
+
if (prefixMatch) {
|
|
95
|
+
max = Math.max(max, parseInt(prefixMatch[1], 10));
|
|
96
|
+
continue;
|
|
97
|
+
}
|
|
98
|
+
if (!isRecordFile(file))
|
|
99
|
+
continue;
|
|
100
|
+
try {
|
|
101
|
+
const content = await fs.readFile(path.join(dir, file), 'utf-8');
|
|
102
|
+
const region = frontmatterRegion(content);
|
|
103
|
+
if (!region)
|
|
104
|
+
continue;
|
|
105
|
+
const idLine = region.split('\n').find((l) => l.trim().toLowerCase().startsWith('id:'));
|
|
106
|
+
if (!idLine)
|
|
107
|
+
continue;
|
|
108
|
+
const idVal = idLine.slice(idLine.indexOf(':') + 1).trim();
|
|
109
|
+
const idMatch = idVal.match(fromId);
|
|
110
|
+
if (idMatch)
|
|
111
|
+
max = Math.max(max, parseInt(idMatch[1], 10));
|
|
112
|
+
}
|
|
113
|
+
catch {
|
|
114
|
+
// Unreadable record — skip rather than abort the whole allocation.
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
return max;
|
|
118
|
+
}
|
|
119
|
+
export async function scanMaxIssueNumber(issuesDir) {
|
|
120
|
+
return scanMaxRecordNumber(issuesDir, 'ISS');
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* Allocate the next PREFIX-NNNN and write the record, with both held under one
|
|
124
|
+
* lock so no other process can allocate the same id in between.
|
|
125
|
+
*
|
|
126
|
+
* `buildContent` receives the allocated id because the id appears inside the
|
|
127
|
+
* record body (frontmatter `id:`), so content cannot be built before allocation.
|
|
128
|
+
*
|
|
129
|
+
* On the rare EEXIST — a bypassing writer took the id between our scan and our
|
|
130
|
+
* write — the allocation is retried rather than failed, up to `maxAttempts`.
|
|
131
|
+
* Retrying is correct here: the id is genuinely taken, and the next one up is a
|
|
132
|
+
* valid answer to the caller's request.
|
|
133
|
+
*/
|
|
134
|
+
export async function allocateAndWriteRecord(dir, prefix, extension, slug, buildContent, options = {}) {
|
|
135
|
+
const maxAttempts = options.maxAttempts ?? 5;
|
|
136
|
+
const lockPath = path.join(dir, LOCK_FILENAME);
|
|
137
|
+
return withFileLock(lockPath, async () => {
|
|
138
|
+
await sweepAllocationTemps(dir);
|
|
139
|
+
let next = (await scanMaxRecordNumber(dir, prefix)) + 1;
|
|
140
|
+
for (let attempt = 0; attempt < maxAttempts; attempt++) {
|
|
141
|
+
const id = formatRecordId(prefix, next);
|
|
142
|
+
const filename = `${id}-${slug}.${extension}`;
|
|
143
|
+
const filePath = path.join(dir, filename);
|
|
144
|
+
options.validate?.(filePath);
|
|
145
|
+
try {
|
|
146
|
+
// Exclusive (EEXIST rather than clobbering) and atomic (never partial).
|
|
147
|
+
await createFileExclusive(filePath, buildContent(id));
|
|
148
|
+
return { id, filename, filePath };
|
|
149
|
+
}
|
|
150
|
+
catch (err) {
|
|
151
|
+
if (err.code !== 'EEXIST')
|
|
152
|
+
throw err;
|
|
153
|
+
next++; // Taken by a writer that did not hold the lock. Try the next.
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
throw new Error(`Could not allocate an unused ${prefix} id in ${dir} after ${maxAttempts} attempts ` +
|
|
157
|
+
`(last tried ${formatRecordId(prefix, next - 1)}). The directory may contain records ` +
|
|
158
|
+
`written by a process that bypasses id locking.`);
|
|
159
|
+
}, { timeoutMs: options.timeoutMs });
|
|
160
|
+
}
|
|
161
|
+
/** ISS-NNNN allocation. See {@link allocateAndWriteRecord}. */
|
|
162
|
+
export async function allocateAndWriteIssue(issuesDir, extension, slug, buildContent, options = {}) {
|
|
163
|
+
return allocateAndWriteRecord(issuesDir, 'ISS', extension, slug, buildContent, options);
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* EPIC-NNNN allocation.
|
|
167
|
+
*
|
|
168
|
+
* Added because `logEpic` had the pre-lock defect verbatim: it read the
|
|
169
|
+
* directory for the current maximum, then wrote the file ~20 awaits later.
|
|
170
|
+
* Epics are the worse half of that race. An issue collision at least left two
|
|
171
|
+
* files and a visible duplicate id; the epic path wrote through
|
|
172
|
+
* `writeFileAtomic`, whose rename CLOBBERS, so the loser was not duplicated —
|
|
173
|
+
* it was gone, and its caller had already been told it was created. Found by
|
|
174
|
+
* the S5 concurrency sweep.
|
|
175
|
+
*/
|
|
176
|
+
export async function allocateAndWriteEpic(epicsDir, slug, buildContent, options = {}) {
|
|
177
|
+
return allocateAndWriteRecord(epicsDir, 'EPIC', 'md', slug, buildContent, options);
|
|
178
|
+
}
|
|
179
|
+
//# sourceMappingURL=recordIdAllocator.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"recordIdAllocator.js","sourceRoot":"","sources":["../../src/lib/recordIdAllocator.ts"],"names":[],"mappings":"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"}
|
package/dist/orgId.d.ts
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
export declare class OrgIdError extends Error {
|
|
2
|
+
}
|
|
3
|
+
/**
|
|
4
|
+
* The configured org, or null when DECIBEL_ORG_ID is unset or blank.
|
|
5
|
+
* A value that is set but not a uuid throws: a typo must not look like "unset".
|
|
6
|
+
*/
|
|
7
|
+
export declare function getOrgId(): string | null;
|
|
8
|
+
/** The configured org, or an actionable error naming the variable. */
|
|
9
|
+
export declare function requireOrgId(): string;
|
|
10
|
+
/** True when this process holds HQ service credentials. */
|
|
11
|
+
export declare function isHqConnected(): boolean;
|
|
12
|
+
/**
|
|
13
|
+
* Startup gate. Throws when HQ credentials are present but no valid org is.
|
|
14
|
+
* Returns the org when connected, or null for a local-only install.
|
|
15
|
+
*/
|
|
16
|
+
export declare function assertOrgForHqConnection(): string | null;
|
|
17
|
+
/**
|
|
18
|
+
* For the HQ writers' entry points, which must never throw into the daemon:
|
|
19
|
+
* the org, or null after logging why the feature is off. Reaching null on the
|
|
20
|
+
* daemon path means the startup gate was skipped.
|
|
21
|
+
*/
|
|
22
|
+
export declare function orgOrLog(feature: string): string | null;
|
|
23
|
+
//# sourceMappingURL=orgId.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"orgId.d.ts","sourceRoot":"","sources":["../src/orgId.ts"],"names":[],"mappings":"AAqBA,qBAAa,UAAW,SAAQ,KAAK;CAAG;AAExC;;;GAGG;AACH,wBAAgB,QAAQ,IAAI,MAAM,GAAG,IAAI,CAUxC;AAED,sEAAsE;AACtE,wBAAgB,YAAY,IAAI,MAAM,CAWrC;AAED,2DAA2D;AAC3D,wBAAgB,aAAa,IAAI,OAAO,CAEvC;AAED;;;GAGG;AACH,wBAAgB,wBAAwB,IAAI,MAAM,GAAG,IAAI,CAGxD;AAED;;;;GAIG;AACH,wBAAgB,QAAQ,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAOvD"}
|
package/dist/orgId.js
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
// ============================================================================
|
|
2
|
+
// The org this daemon serves: one source (EPIC-0040, ADR-0014)
|
|
3
|
+
// ============================================================================
|
|
4
|
+
// There is no default. This used to fall back to a hardcoded uuid, Ben's org,
|
|
5
|
+
// so an HQ-connected daemon that forgot DECIBEL_ORG_ID silently wrote presence
|
|
6
|
+
// and command settlements into that org under service_role. RLS sits below
|
|
7
|
+
// service_role, so nothing caught it. Once HQ gives every account a personal
|
|
8
|
+
// org (decibel-hq ADR-0016), that fallback becomes a cross-tenant write on
|
|
9
|
+
// every solo install, which is why removing it is step (a) of ADR-0014.
|
|
10
|
+
//
|
|
11
|
+
// The org is required exactly where it is used: the HQ-connected path, i.e.
|
|
12
|
+
// SUPABASE_URL and SUPABASE_SERVICE_KEY are set. A local-only install never
|
|
13
|
+
// reads it. assertOrgForHqConnection() is the daemon's startup gate; the
|
|
14
|
+
// readers call requireOrgId() lazily, never at import time, so a missing
|
|
15
|
+
// variable cannot break boot for a process that would never use it.
|
|
16
|
+
// ============================================================================
|
|
17
|
+
import { log } from './config.js';
|
|
18
|
+
const UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
19
|
+
export class OrgIdError extends Error {
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* The configured org, or null when DECIBEL_ORG_ID is unset or blank.
|
|
23
|
+
* A value that is set but not a uuid throws: a typo must not look like "unset".
|
|
24
|
+
*/
|
|
25
|
+
export function getOrgId() {
|
|
26
|
+
const raw = process.env.DECIBEL_ORG_ID?.trim();
|
|
27
|
+
if (!raw)
|
|
28
|
+
return null;
|
|
29
|
+
if (!UUID_RE.test(raw)) {
|
|
30
|
+
throw new OrgIdError('DECIBEL_ORG_ID is set but is not a uuid. It must be the id of your org ' +
|
|
31
|
+
'in HQ (hq.orgs.id).');
|
|
32
|
+
}
|
|
33
|
+
return raw.toLowerCase();
|
|
34
|
+
}
|
|
35
|
+
/** The configured org, or an actionable error naming the variable. */
|
|
36
|
+
export function requireOrgId() {
|
|
37
|
+
const org = getOrgId();
|
|
38
|
+
if (!org) {
|
|
39
|
+
throw new OrgIdError('DECIBEL_ORG_ID is not set. HQ-connected features (presence, agent commands, ' +
|
|
40
|
+
'roster resolution) need the id of the org this machine works for. Set ' +
|
|
41
|
+
'DECIBEL_ORG_ID to your org id from HQ (hq.orgs.id) in the daemon environment. ' +
|
|
42
|
+
'There is no default, so writes cannot land in someone else\'s org.');
|
|
43
|
+
}
|
|
44
|
+
return org;
|
|
45
|
+
}
|
|
46
|
+
/** True when this process holds HQ service credentials. */
|
|
47
|
+
export function isHqConnected() {
|
|
48
|
+
return Boolean(process.env.SUPABASE_URL && process.env.SUPABASE_SERVICE_KEY);
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Startup gate. Throws when HQ credentials are present but no valid org is.
|
|
52
|
+
* Returns the org when connected, or null for a local-only install.
|
|
53
|
+
*/
|
|
54
|
+
export function assertOrgForHqConnection() {
|
|
55
|
+
if (!isHqConnected())
|
|
56
|
+
return null;
|
|
57
|
+
return requireOrgId();
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* For the HQ writers' entry points, which must never throw into the daemon:
|
|
61
|
+
* the org, or null after logging why the feature is off. Reaching null on the
|
|
62
|
+
* daemon path means the startup gate was skipped.
|
|
63
|
+
*/
|
|
64
|
+
export function orgOrLog(feature) {
|
|
65
|
+
try {
|
|
66
|
+
return requireOrgId();
|
|
67
|
+
}
|
|
68
|
+
catch (e) {
|
|
69
|
+
log(`${feature}: disabled. ${e instanceof Error ? e.message : String(e)}`);
|
|
70
|
+
return null;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
//# sourceMappingURL=orgId.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"orgId.js","sourceRoot":"","sources":["../src/orgId.ts"],"names":[],"mappings":"AAAA,+EAA+E;AAC/E,+DAA+D;AAC/D,+EAA+E;AAC/E,8EAA8E;AAC9E,+EAA+E;AAC/E,2EAA2E;AAC3E,6EAA6E;AAC7E,2EAA2E;AAC3E,wEAAwE;AACxE,EAAE;AACF,4EAA4E;AAC5E,4EAA4E;AAC5E,yEAAyE;AACzE,yEAAyE;AACzE,oEAAoE;AACpE,+EAA+E;AAE/E,OAAO,EAAE,GAAG,EAAE,MAAM,aAAa,CAAC;AAElC,MAAM,OAAO,GAAG,iEAAiE,CAAC;AAElF,MAAM,OAAO,UAAW,SAAQ,KAAK;CAAG;AAExC;;;GAGG;AACH,MAAM,UAAU,QAAQ;IACtB,MAAM,GAAG,GAAG,OAAO,CAAC,GAAG,CAAC,cAAc,EAAE,IAAI,EAAE,CAAC;IAC/C,IAAI,CAAC,GAAG;QAAE,OAAO,IAAI,CAAC;IACtB,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC;QACvB,MAAM,IAAI,UAAU,CAClB,yEAAyE;YACvE,qBAAqB,CACxB,CAAC;IACJ,CAAC;IACD,OAAO,GAAG,CAAC,WAAW,EAAE,CAAC;AAC3B,CAAC;AAED,sEAAsE;AACtE,MAAM,UAAU,YAAY;IAC1B,MAAM,GAAG,GAAG,QAAQ,EAAE,CAAC;IACvB,IAAI,CAAC,GAAG,EAAE,CAAC;QACT,MAAM,IAAI,UAAU,CAClB,8EAA8E;YAC5E,wEAAwE;YACxE,gFAAgF;YAChF,oEAAoE,CACvE,CAAC;IACJ,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC;AAED,2DAA2D;AAC3D,MAAM,UAAU,aAAa;IAC3B,OAAO,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC,YAAY,IAAI,OAAO,CAAC,GAAG,CAAC,oBAAoB,CAAC,CAAC;AAC/E,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,wBAAwB;IACtC,IAAI,CAAC,aAAa,EAAE;QAAE,OAAO,IAAI,CAAC;IAClC,OAAO,YAAY,EAAE,CAAC;AACxB,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,QAAQ,CAAC,OAAe;IACtC,IAAI,CAAC;QACH,OAAO,YAAY,EAAE,CAAC;IACxB,CAAC;IAAC,OAAO,CAAC,EAAE,CAAC;QACX,GAAG,CAAC,GAAG,OAAO,eAAe,CAAC,YAAY,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;QAC3E,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pairCli.d.ts","sourceRoot":"","sources":["../src/pairCli.ts"],"names":[],"mappings":"AAwCA,wBAAsB,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,MAAM,CAAC,CA0DhE"}
|
package/dist/pairCli.js
ADDED
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
// ============================================================================
|
|
2
|
+
// `decibel-tools pair` — the only place a browser pairing is approved
|
|
3
|
+
// ============================================================================
|
|
4
|
+
// ADR-0012 (this repo): approval is unreachable from any browser. It happens
|
|
5
|
+
// here, in a terminal on this machine, by typing the code HQ is showing — which
|
|
6
|
+
// proves the person approving can see both sides. `pair list` deliberately does
|
|
7
|
+
// NOT print pending codes: a code read from here instead of from HQ proves
|
|
8
|
+
// nothing.
|
|
9
|
+
// ============================================================================
|
|
10
|
+
import { createInterface } from 'readline';
|
|
11
|
+
import { approveByCode, listPairings, pairingsPath, revokePairing } from './pty/pairing.js';
|
|
12
|
+
function ago(iso) {
|
|
13
|
+
if (!iso)
|
|
14
|
+
return '-';
|
|
15
|
+
const s = Math.max(0, Math.round((Date.now() - Date.parse(iso)) / 1000));
|
|
16
|
+
if (s < 60)
|
|
17
|
+
return `${s}s ago`;
|
|
18
|
+
if (s < 3600)
|
|
19
|
+
return `${Math.round(s / 60)}m ago`;
|
|
20
|
+
if (s < 86400)
|
|
21
|
+
return `${Math.round(s / 3600)}h ago`;
|
|
22
|
+
return `${Math.round(s / 86400)}d ago`;
|
|
23
|
+
}
|
|
24
|
+
function describe(p) {
|
|
25
|
+
return `${p.origin} "${p.label}" requested ${ago(p.requested_at)}`;
|
|
26
|
+
}
|
|
27
|
+
async function confirm(question) {
|
|
28
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
29
|
+
const answer = await new Promise((resolve) => rl.question(question, resolve));
|
|
30
|
+
rl.close();
|
|
31
|
+
return /^y(es)?$/i.test(answer.trim());
|
|
32
|
+
}
|
|
33
|
+
const USAGE = `Usage:
|
|
34
|
+
decibel-tools pair list pending requests and approved browsers
|
|
35
|
+
decibel-tools pair approve <code> approve the request showing <code> in HQ
|
|
36
|
+
decibel-tools pair revoke <id> remove a pairing (closes its live sessions)
|
|
37
|
+
|
|
38
|
+
Store: ${'~/.decibel/pairings.yaml'} (public keys only, mode 0600)`;
|
|
39
|
+
export async function runPairCli(argv) {
|
|
40
|
+
const [cmd, arg, ...rest] = argv;
|
|
41
|
+
const yes = rest.includes('--yes') || arg === '--yes';
|
|
42
|
+
if (cmd === 'list') {
|
|
43
|
+
const all = listPairings();
|
|
44
|
+
const pending = all.filter((p) => p.status === 'pending');
|
|
45
|
+
const approved = all.filter((p) => p.status === 'approved');
|
|
46
|
+
console.log(`Pending (${pending.length}) — approve with the code HQ shows:`);
|
|
47
|
+
for (const p of pending)
|
|
48
|
+
console.log(` ${p.id} ${describe(p)}`);
|
|
49
|
+
console.log(`Approved (${approved.length}):`);
|
|
50
|
+
for (const p of approved)
|
|
51
|
+
console.log(` ${p.id} ${p.origin} "${p.label}" approved ${ago(p.approved_at)}, last used ${ago(p.last_used)}`);
|
|
52
|
+
if (!all.length)
|
|
53
|
+
console.log(` (none — ${pairingsPath()})`);
|
|
54
|
+
return 0;
|
|
55
|
+
}
|
|
56
|
+
if (cmd === 'approve') {
|
|
57
|
+
if (!arg || !/^\d{6}$/.test(arg)) {
|
|
58
|
+
console.error('Type the 6-digit code HQ is showing: decibel-tools pair approve <code>');
|
|
59
|
+
return 2;
|
|
60
|
+
}
|
|
61
|
+
const pending = listPairings().filter((p) => p.status === 'pending');
|
|
62
|
+
const target = pending.find((p) => p.code === arg);
|
|
63
|
+
if (!target) {
|
|
64
|
+
console.error(`No pending request with code ${arg}. Requests expire after 10 minutes; start again in HQ.`);
|
|
65
|
+
return 1;
|
|
66
|
+
}
|
|
67
|
+
console.log(`Pairing request: ${describe(target)}`);
|
|
68
|
+
if (pending.length > 1) {
|
|
69
|
+
console.log(`WARNING: ${pending.length} requests are pending at once. If you did not start all of them, approve nothing and check HQ.`);
|
|
70
|
+
}
|
|
71
|
+
if (!yes) {
|
|
72
|
+
if (!process.stdin.isTTY) {
|
|
73
|
+
console.error('Refusing to approve without a terminal to confirm in. Re-run interactively (or pass --yes if you are certain).');
|
|
74
|
+
return 1;
|
|
75
|
+
}
|
|
76
|
+
if (!(await confirm(`Let ${target.origin} act on this machine's daemon? [y/N] `))) {
|
|
77
|
+
console.log('Not approved.');
|
|
78
|
+
return 1;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
const p = await approveByCode(arg);
|
|
82
|
+
console.log(`Approved ${p.id} (${p.origin}). Revoke any time: decibel-tools pair revoke ${p.id}`);
|
|
83
|
+
return 0;
|
|
84
|
+
}
|
|
85
|
+
if (cmd === 'revoke') {
|
|
86
|
+
if (!arg) {
|
|
87
|
+
console.error('decibel-tools pair revoke <id> (ids: decibel-tools pair list)');
|
|
88
|
+
return 2;
|
|
89
|
+
}
|
|
90
|
+
const removed = await revokePairing(arg);
|
|
91
|
+
console.log(removed ? `Revoked ${arg}. Its live sessions close within 5s.` : `No pairing ${arg}.`);
|
|
92
|
+
return removed ? 0 : 1;
|
|
93
|
+
}
|
|
94
|
+
console.log(USAGE);
|
|
95
|
+
return cmd ? 2 : 0;
|
|
96
|
+
}
|
|
97
|
+
//# sourceMappingURL=pairCli.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pairCli.js","sourceRoot":"","sources":["../src/pairCli.ts"],"names":[],"mappings":"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"}
|