Repository navigation
OODA: signals the loop can see (PR 2 of 5) - #200
Merged
Merged
Conversation
The owner can't tell where an observation came from or where it sits in the loop. The schema now maps each type and observation kind to its stage (x-ooda.stages) and names the sources (x-ooda.sources). Both are computed when a record is read, never stored: source and detector already mean things older kits read (a sensor's name, repo or prod on a goal reading, detector sensor on the kit's own lines), so rewriting them would have an older kit on a mixed fleet misread new lines. log --json, the dashboard's observations and peek carry stage and an observation's provenance. observe --source refuses the kit's own sources and another writer's; older free text still writes as given and reads as its writer's. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
On Stacks four spikes fired after the box was down 03Z-13Z: the spike check filled the hours nobody was listening with 0, so the median was 0 and a normal hour passed. Each sensor now records the hours it heard (listened.json beside counts.json: a check that ran, or events that arrived), and the median is over those alone, 24 of them at least. The file is pruned with the counts. A spike now names the fingerprint's id, frames a log template as untrusted production data, as log-error does, and counts whole days (floor), never "0 day(s)". A sensor just upgraded has no heard hours yet, so no spike fires for its first 24 heard hours. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…at they set aside A spike's source_key carries its hour, and Orient grouped on source_key, so no two spikes ever shared a key and Stacks's spikes could never reach Orient. Orient now keys a sensor's qualifies and spikes on the fingerprint's id (signalKeyOf). "Say so in an observation" silenced nothing. A noise ruling is now a decision, orient-noise (constitutional: correctable, no rung), that must cite the observations it explains; Orient treats ids a live decision cites as explained, so the owner's correction of a ruling brings the signal back. The Decide feed doesn't call a ruling "not acted on yet". ack and sensor ignore now cite the observations they set aside, the newest 40 to keep the line small, with the finding's key or the fingerprint in source_ref for the rest. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
incident-unowned needs an incident to exist first, so a sensor's escalation that nobody folded, ignored or ruled noise was never raised again: Stacks's spikes sat untouched. sensor-untriaged names each fingerprint whose escalation of severity 3 or more is older than sensor_defaults.triage_overdue_hours (default 2) while the supervisor runs, with no incident holding it, no ignore and no live noise ruling citing it. Once an incident holds it, incident-unowned takes over. It looks back 7 days, so an upgrade doesn't raise every escalation the log ever held, and skips sensors no longer in the settings, as triage does. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
supervisor.md's Observe now says a reading is a measurement and never a signal, and that an observation's provenance and stage are computed (read provenance.from, not source). The spec's §2 is rewritten for the read-time design and why: source and detector already mean things older kits read, so rewriting them would break a mixed fleet. It also records the listened.json file, the 7-day look-back of sensor-untriaged, and that fix-regressed and recurred keep their own recurrence keys. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The one-line hint after orient's signals changed the single-repo golden (G1), which is never re-recorded; supervisor.md step 4 already says how to explain a signal. stageOf looks a kind up with Object.hasOwn, so an observation of kind "constructor" reads as Observe. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ignal Review fixes: one definition of what explains an observation (explained()), shared by Orient and sensor-untriaged. A live orient-noise ruling that names a signal key covers that signal's later observations, and an open incident holds its fingerprints. The orient test no longer fails before noon UTC (its lines span five days). sensor-reconsidered is a Decide-stage set-aside, and a hand-written goal reading is refused. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Review fixes from the upstream loop: an owner-* source on a line the owner didn't write needs a ref that checks out (an answered question, the owner's turn or thread), or it reads as its writer's; observe refuses it without one. thread-resolved reads as the owner's only from the owner. The supervisor no longer writes source worker or planner. A set-aside's refs are trimmed to fit beside a long reason. supervisor.md says when to use --source owner-chat. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Review pushback from the upstream loop: a ruling that names a signal key covered the fingerprint forever, so a later escalation (same template, another cause) was never asked to be triaged. It now covers the signal's observations up to the ruling; one after it gets a fresh look, in Orient and in sensor-untriaged. A fingerprint that is always noise is for sensor ignore. Tested both ways. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
dashboard-data's observations now carry stage and provenance, and peek ticket says stage null. Each is a key added beside what was there (the only removed lines are ones that gained a trailing comma), so the golden is recorded again rather than the views narrowed. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… new Orient words) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Member
Author
|
Fable reviewed it, and its findings are fixed: owner-* provenance needs a verified ref, thread-resolved reads as owner-thread only from the owner, the supervisor can't claim worker or planner sources, and a noise ruling covers only observations before it. Every box instance is now on v0.41.1 or later, so its log lines are tolerated. CI 9/9 at 350237b with main merged in. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
PR 2 of 5 for the inspectable OODA loop (spec and plan in #193). Stacked on #193; GitHub retargets this to main when #193 merges.
What changes
x-ooda: each record type and observation kind mapped to its stage (Observe, Orient, Decide, Act, Learn, or measurement/inbox), and the fixed source vocabulary (sensor,metrics,spend,owner,owner-answer,owner-thread,owner-chat,retro,worker,planner,supervisor).stageOf(o)andprovenanceOf(o)compute them; nothing in storage changes, so older kits read new lines as before.log --json,dashboard-dataand the dashboard's observations carrystageandprovenance.observe --sourcekeeps to the vocabulary: the kit's own sources (sensor,metrics,spend) and another writer's are refused.run/sensors/<name>/listened.jsonmarks every hour a probe ran or a stream listened. The median is over those hours only, and a spike needs 24 of them. This is what raised four false spikes on Stacks after its box was down overnight. The detail now names the fingerprint id, with the template inside the untrusted-data frame.source_key, so they recur. Any observation a live decision cites counts as explained.decide --class orient-noise --refs <signal key> <o-…> -- "<why>", a constitutional class. It clears the signal, and an owner's correction brings it back.ackrefs the finding's observations;sensor ignorerefs the fingerprint's observations; newest 40 of each.sensor-untriaged: a sensor escalation of severity 3+ older thantriage_overdue_hours(2) with no incident, ignore or noise ruling, looking back 7 days, while the supervisor runs.supervisor.md§3 (readings vs observations, noise rulings, signals by fingerprint) and constitution §2 (orient-noise).On upgrade
listened.jsonraises no spikes until it has heard 24 hours (about a day). Qualifies, downs and incidents are unaffected.log --jsonkeys. Every object getsstage, and observations getprovenance.For the operator's review (#190 follow-up)
Read
provenance.from(inlog --json, an operator read) instead ofobservation.source, withx-ooda.sourcesas the vocabulary.metricslistssensor-untriaged. The weeklylearningrecord and orientation debt come in PRs 3–5.Tests
test/ooda-signals.test.mjs(6 tests): provenance and stage for every writer,observe --sourcerefusals, spikes with a listening gap, one signal per fingerprint and the noise ruling's correction, set-aside refs and the 41 → 40 cut,sensor-untriagedand its hand-off, thekitlesson tag. The chain's spike step seedslistened.json. Full suite on Node 24: 377 passed, 0 failed, 1 skipped. CI covers 18/20/22/24.One-way doors
None. Storage is untouched apart from
observe --sourcerefusing values outside the vocabulary.🤖 Generated with Claude Code