מסמך מקור מתקדם. דפי ההתקנה, פרוטוקול ההפעלה וששת הסקילים זמינים לקריאה בעברית; קובץ המקור נשמר כפי שהוא.
V1 local durable worker — 24 October 2026
This is an offline synthetic process implementation, not a cloud service or a live Meta→Sheets proof. Python standard library, SQLite and Unix fcntl are required. Nothing installs a daemon, creates credentials, sends email, or accesses a network.
yskit.worker_v2.DurableWorker(db_path, scope, port) owns a SQLite inbox, state transitions, latest observations and an append-only local outbox. Scope is an exact single-tenant allowlist of tenant_id, account_id, page_id, form_id. Each event must match all four, have source: "meta", a nonempty source_lead_id and synthetic: true. There is no fallback. Database scope is pinned at creation. The database and lock must reside on a local filesystem, in an operator-owned private directory, isolated per tenant; application checks are not an OS security boundary. SQLite files contain fixture intake fields and must never receive real PII in this version.
accept(record) commits before returning acknowledgement. The composite key is tenant+source_lead_id. Identical canonical JSON replays return duplicate; changed content under the same key is rejected. Durable write_id is allocated once and retained across restart. The transactional outbox records write state and status observations; it does not claim SMTP acceptance or message delivery.
A nonblocking OS file lock covers the entire local send/reconcile cycle. A second local worker cannot take over while the old process is alive, including during an overdue request. On lock acquisition after a crash, in-flight records become uncertain. A crash before dispatch is conservatively uncertain too. This sacrifices liveness instead of risking duplicate external writes. Local locking cannot fence a request already accepted by an external target.
States are prepared → in-flight → confirmed, failed-safe, or uncertain. Only the typed integrations_v2.BeforeWriteError (guaranteed undispatched request) permits another send. Every other exception or incomplete result becomes uncertain. Uncertain records call reconcile only; no timer, empty read count, or restart authorizes retry. Confirmation requires state: done with the same write_id from a trusted adapter providing positive exact readback. CRMRecordPort(adapter) explicitly maps validated scoped events to the Sheets adapter's narrower tenant/source/source_lead_id/is_test/contact_alias record. It is an interface bridge, not live access approval.
observe(record, snapshot) atomically stores a latest status observation and an outbox event. A missing confirmed row becomes confirmed_then_missing and is never automatically recreated. Observations are latest snapshots, not a claim to full intervening history, verified human identity, qualified sale, or revenue. The foreground start loop polls the source after each write/reconcile cycle through port.observe_status. Polling defaults to at most 100 confirmed leads per cycle (configurable 1–1000), ordered by oldest polling time for fairness. Only changed snapshots produce status outbox events. Failed/ambiguous reads retain the last business snapshot and persist deduplicated poll-health events; they never imply deletion. Explicit missing observations stop automatic recreation. There is no outbound notification dispatcher in this slice.
CLI
Prepare scope.json containing the four scope fields, and event.json containing those plus source, source_lead_id, synthetic and optional contact_alias. Use separate absolute paths for queue and synthetic target:
python3 tools/run_worker_v2.py accept --db /tmp/kit-queue.db --scope /tmp/scope.json --target /tmp/kit-target.db --record /tmp/event.json
python3 tools/run_worker_v2.py once --db /tmp/kit-queue.db --scope /tmp/scope.json --target /tmp/kit-target.db
python3 tools/run_worker_v2.py start --db /tmp/kit-queue.db --scope /tmp/scope.json --target /tmp/kit-target.db --interval 1 --cycles 3
python3 tools/run_worker_v2.py status --db /tmp/kit-queue.db --scope /tmp/scope.json --target /tmp/kit-target.db
python3 tools/run_worker_v2.py observe --db /tmp/kit-queue.db --scope /tmp/scope.json --target /tmp/kit-target.db --record /tmp/event.json --snapshot /tmp/status.json
poll-status runs one bounded source-observation pass; --poll-limit defaults to 100. start runs both writes and status observations in an independent foreground process; omit --cycles for continuous foreground operation and stop with Ctrl-C/SIGTERM. No daemon or startup registration is installed. --fault outage injects a definite pre-send failure; timeout writes then returns uncertain; crash-after-write exits process with code 73 after target commit. Restart without the fault reconciles by exact durable target evidence. status emits JSON; there is no separate export command.
Observed evidence and limits
Run python3 -m unittest discover -s tests -p test_worker_v2.py -v. Tests cover exact replay/conflict, wrong tenant/account/page/form, changed database scope, safe pre-send outage retry, repeated unresolved reconciliation without a second send, positive readback after timeout, old-worker exclusion, crash before send, latest status/outbox and missing-row behavior, and 100 events across restart.
The subprocess test waits for an ingress CLI process to exit, then runs another worker process that writes and crashes, then a fresh process confirms through reconciliation. The final target has one row and attempts remains one. This proves persisted local process separation after the first producer ends. The chat host was not terminated, no tools-free window or independent supervisor/watchdog was demonstrated, and this is explicitly not host-closed or cloud proof. A 30-minute endurance run, external negative alert test, 429 timing/backoff, supervisor ownership, sleep/reboot, authenticated webhook verification, and live integration remain untested here. No liveness or five-minute delivery guarantee is made; unresolved uncertain events stay visible.
Follow-up verification also covers changed/unchanged status polling, outage retention, fairness bound, explicit missing, restart, and a separate start process observing a source status mutation after the ingress process exited. This remains local process proof only.
Acceptance sources: Astra gap spec sections on durable queue/reconciliation and Claude disposition item 2/7, read 24 October 2026. Legacy simulation.py remains unchanged.