The ask ledger: the first part of the Factory bet's owner-policy module (M4) and the instrument behind its first Key result (KR1). One journal aggregate records the asks put to the owner as its channels report them, the owner's answers, minutes, estimates and rules, the updates sent and the weekly note, and counts them per ISO week in the owner's zone. It decides nothing, sends nothing and grants nothing: a rule is recorded in the owner's words, never applied and never inferred.
Status: Accepted local implementation: independent Astra reviews and combined checks passed. No live asks or applied rules. Acceptance and evidence.
- Source:
src/owner-policy.ts - Tests:
test/owner-policy.test.ts(46),test/ask-ledger-adversarial.test.ts(9, the adversary's first round),test/ask-ledger-r2-adversarial.test.ts(2, its second),test/ask-ledger-r3-adversarial.test.ts(4, its third),test/owner-policy-fixture.test.ts(1),test/owner-policy-processes.test.ts(3, real processes). What each covers: Tests and helpers. - Helpers:
test/helpers/owner-policy.ts,test/helpers/owner-policy-child.ts - Plan:
<local evidence archive>(KR1 in §2, M4 in §3, H3 in §4; Astra round 3 PASS on the plan). Build logs and dispositions:<local evidence archive>. - Human page: guide
Intelligence: none — Counts asks, minutes and repeats exactly and records a rule only in the owner's own words; a model would infer rules the owner never made.
What it hides
One aggregate, one decision.
owner-policy:ledgerholds the channel lists, the asks with their answers, the updates, the weekly notes, KR1's freeze and the full mark. Each command is oneCommandJournal.execute. Its decision re-parses the stored state,CORRUPTon any defect and never repaired. It reads the injected clock once, applies the ledger's rules and re-validates the state it stores.Command IDs are journal-wide and distinct from every other module's:
owner-policy/channels/<revision>,owner-policy/ask/<askId>,owner-policy/ask/<askId>/answer,owner-policy/update/<updateId>andowner-policy/note/<ISO week>. The payload is the normalised request (channels, effects and a rule's Products sorted; an absent optional field as null), and the clock stays outside it.Replay. Each record keeps the version its command produced. A retry is executed again at that version, so the journal answers from its receipt: an identical request replays (
replayed: true, with the originalrecordedAt), and any other isCONFLICT. A receipt another writer recorded after the plan was read is found by planning again, at mostmaxRounds(16) times, thenCONTENDED.Version accounting. Every command writes exactly one record and stores its version there. The stored versions are therefore exactly 1 to the aggregate's version, the first channel list holding 1, and a version no record holds is
CORRUPT.The journal's order is the sequence; instants are claims. The versions are the journal's commit order. Each record's
recordedAtis its recorder's clock reading. An ask'saskedAt, an update'ssentAt, and an answer's or a note'sansweredAtis the instant it states, which may come from another clock. All of them are claims. No command is refused because its clock reads earlier than an earlier record's claim, or because the instant it states is later than its ownrecordedAt; only an answer must still be stated no earlier than its own ask'saskedAt. The ledger's time,lastRecordedAt, is the latest instant any record claims.The owner's weeks. Instants are UTC texts with milliseconds (
2026-10-05T08:00:00.000Z), from 1970 to the end of 9997, so every week one falls in has its settlement. Weeks are ISO weeks in the ledger's zone, Monday 00:00 to Monday 00:00, worked out with three of the Lifecycle schedule's pure zone helpers. A channel list takes effect strictly after the later of its own instant and every instant earlier records claim, so it is never backdated.Settlement and late records. No ask or update is refused for being late: every report is kept. A period settles once the week after it has ended on the reader's own clock and a record has claimed an instant since. Only a settled period can read exact. A record whose own clock read past its week's settlement is late: it is counted, and every period holding that week, a KR1 window included, reads "at least" with
late-record. One whose own clock read earlier, while the ledger's time had already passed that settlement, carriesclock-disagreementto those periods instead. Either way, a record that arrives after a period was read settled marks it, so an exact figure never changes silently, for the same resolved classes.Clock disagreement. Three kinds of evidence show that clocks disagree, and the ledger cannot tell which clock was wrong:
- A record whose recorder's clock reads earlier than the ledger's time before it, an instant an earlier record claims. The span runs from the ledger's time before the first record claiming a later instant to the next record recorded in order again, and stays open until one arrives. The recording decides, so a stated instant ahead of the ledger's time never hides a recorder behind it.
- A record stating an instant after its own
recordedAt. The span runs from the one to the other. - Records claiming instants ahead of the reader's clock. The span runs from the ledger's time before the first of them to the ledger's time.
Where a disagreement counts. A span makes a period's Counts "at least" with
clock-disagreementonly where it reaches across an instant they rest on, since only there could a record made in it truly lie on the other side:- the period's start or end;
- the settlement of one of its weeks, when the span holds one of that week's asks or updates, whose lateness then rests on which clock was right;
- for KR1's windows, the start or end of the week the freeze was recorded in, when the span holds the freeze's instant, and the instant the ledger went live, since the windows' dates follow from both.
A span wholly between those instants moves no record out of the period and changes no Count. A note's figures carry
clock-disagreementonly when a span holding one of the note's own instants reaches back across its week's end.PeriodReport.disputescounts the period's asks and updates in a disagreement that makes its Counts "at least", and names who recorded every record in one that reaches its Counts or notes.AskView.disputedmarks an ask whose own record is in a disagreement between records.Evidence of an incomplete list. An ask or update through a channel the list in force does not name proves that list revision incomplete for as long as it was in force, since when the channel began to reach the owner is not known. Every period it covers carries
unlisted-channel.Minutes are counted in units of 1/175 minute, so whole minutes and words read add exactly; a Count's value is the units divided by 175. An ask's minutes are settled only by an answer that reports them.
Estimates and rehearsal. The owner's do-it-yourself estimate for a class comes with the class's first live answer in the journal's order, or never. A rehearsal answer may carry an estimate and a rule, which have no standing: the estimate is never the class's, the rule's view says it is not live, and
classes()counts rehearsal answers apart.
Public interface
src/owner-policy.ts. Runtime imports: Buffer (node:buffer), canonicalJson (./canonical-json.ts), CommandJournal and its conflict and decision errors (./journal.ts), and isoWeekOf, localDateOf, zonedInstant and ScheduleError (./lifecycle-schedule.ts). Portfolio lends only its Count type. Nothing under src/ imports it.
- Constants:
LEDGER_PROTOCOL = "sf-ask-ledger/1",LEDGER_AGGREGATE = "owner-policy:ledger",READING_WORDS_PER_MINUTE = 175,ASK_KINDS,RESERVED_EFFECTS(account,payment,push,terms) andFEELINGS(delight,frustration,neither).LEDGER_LIMITS(frozen):maxRevision999,maxChannels16,maxSubjectLength200,maxRuleWordsLength280,maxAskMinutes1,440 (also the bound on an estimate),maxWeekMinutes10,080,maxUpdateWords20,000,maxRuleProducts16,maxResolvedClasses256,maxPage100,maxRounds16,minStateBytes8,192,minHeadroomBytes4,096,softStateBytes655,360 andmaxStateBytes786,432. LEDGER_REASONSandREASON_PHRASES: every reason a Count can carry, each with one plain phrase that completes "at least N, because …" or "unknown, because …", for the digest and the dashboard alike. Both list the reasons in the order a headline takes them. First come whether the period can be counted yet (week-open,not-settled,no-channel-list,rehearsal,ledger-full) and its channels (channel-not-writing,unlisted-channel). Then come when its records were made (clock-disagreement,late-record) and what the owner can still answer (unanswered-asks,no-weekly-note,ask-minutes-missing,unclassed-asks). Last comes what the Factory left out (update-words-missing,resolved-classes-unknown).class AskLedger(journal: CommandJournal, clock: Clock, options?: LedgerOptions).options.limitsmay lower the soft and hard limits, never raise them, and keeps at least 4 KiB between them.recordChannels({revision, zone, live, channels, recordedBy})returnsRecorded {commandId, version, replayed, recordedAt}. Revision 1 opens the ledger and fixes its zone; each channel is{id, writes}.recordAsk({askId, kind, channel, askedAt, subject, productId, effects, minutes, recordedBy})returnsRecordedwithaskId,weekandfroze(whether this ask froze KR1's windows).minutesare those spent so far.answer({askId, answeredAt, class, askedBefore, minutes, diyMinutes?, rule, recordedBy})returnsRecordedwithaskId.minutesis the owner's total, or null when not reported.diyMinutesis the owner's do-it-yourself estimate for the class, taken only with the class's first live answer.ruleis{words, products}or null, andproductsis a list or"all".recordUpdate({updateId, kind, channel, sentAt, words, recordedBy})returnsRecordedwithupdateIdandweek.recordNote({week, outsideAskMinutes, feeling, answeredAt, recordedBy})returnsRecordedwithweek;feelingis the week's one tap, always stated,neitherincluded.- Reads:
read()(LedgerView, or undefined before the first list),channelLists(),ask(askId)(AskView),listAsks({week?, state?, productId?, after?, limit?})(AskPage, in the order asked, then by ID;productIdnull lists the asks about no Product),rules()(RuleViews in the order recorded, rehearsal ones included),classes()(ClassViews by name), andweek(isoWeek, {resolvedClasses?})andkr1({resolvedClasses?}), which returnPeriodReports.
- Pure helpers:
weekOf(zone, instant), the ISO week of an instant in a zone; andheadlineReason(count), the first reason inLEDGER_REASONSa Count carries, null for an exact Count, andINVALIDfor anything but a Count with a ledger reason. - Types:
AskKind(owner-required,agent-question,interjection,parked,live-default),ReservedEffect,UpdateKind(digest,batch),Feeling,LedgerReason,Clock, the five inputs,Recorded,ChannelListView,AskView(minutesso far,totalMinutesonce settled,live,listed,lateby its recorder's clock,disputedandstate),AnswerView(withdiyMinutes),RuleView(withlive, false for a rule recorded in rehearsal, andnever, the four reserved effects),ClassView {class, answered, askedBefore, rules, diy, rehearsal}(live answers, the estimate from the class's first live answer, and rehearsal answers counted apart),LedgerView(withkr1,fullandlastRecordedAt, the ledger's time),Kr1Freeze {askId, askedAt, frozenAt, baseline, evaluation}withWindow {weeks, start, end},ResolvedClass {class, resolvedAt},PeriodReportand Portfolio'sCount. PeriodReport:weeks,start,end,ended(on the reader's clock),settled,live(yes,noorpartly),coverage {complete, reasons},asks {total, byKind},repeats,minutes {total, asks, reading, outside},feelings {delight, frustration},recorded {asks, answered, updates, late, note}anddisputes {records, recordedBy}.- Reason codes:
no-channel-list,rehearsal,channel-not-writing,unlisted-channel,week-open,not-settled,late-record,clock-disagreementandledger-full(coverage: carried by every ask and repeat Count and by the ask, reading and total minutes);unanswered-asks,resolved-classes-unknownandunclassed-asks(repeats);unanswered-asks,ask-minutes-missingandupdate-words-missing(minutes);no-weekly-note,week-openandclock-disagreement(a note's own figures: outside-ask minutes, the taps and the total minutes). AskLedgerErrorCode:INVALID CORRUPT CONFLICT CONTENDED LIMIT NOT_OPENED REVISION UNKNOWN_ASK RULE.
Invariants and guarantees
Each item names the test that holds it ("fixture" is owner-policy-fixture, "processes" is owner-policy-processes, "adversarial" is ask-ledger-adversarial, "adversarial round 2" is ask-ledger-r2-adversarial, "adversarial round 3" is ask-ledger-r3-adversarial, the rest are owner-policy).
- Asks recorded, answered and listed with command-ID replay. An ask is recorded once per ID and answered once. An identical retry replays at any later time and from a restarted process; any other request under the ID is
CONFLICT, and neither writes. Racing recorders record one ask and replay its receipt, and of racing answers the first recorded stands. Tests: "an ask is recorded once per ID: …", "an ask is answered once, …", "asks are listed by week and state, …" and "a request another writer records between this plan and its execution …"; (processes) all three. - Paging holds. The cursor is an ask's place in the order asked, then by ID, which never moves; answering or recording asks between pages never invalidates it. A list may be narrowed by week, state and Product. Tests: "paging holds while asks are answered or recorded between pages: …" and "asks are listed by Product, …"; (adversarial) "paging through the open asks survives …".
- Every kind of ask counts. Owner-required items, agents' questions, reported interjections, parked items and defaults used live unanswered are counted each apart and all in the total. Test: "owner-required, parked and live-defaulted items count as asks, …".
- "Asked before" marks a repeat. The owner's mark counts, and so does an ask in a class the blocker classes resolved before it was asked, once the caller supplies them. An unanswered ask, unknown resolved classes, or an unclassed ask while classes are resolved, leaves repeats "at least". Test: '"asked before" marks a repeat; …'.
- The channel list is recorded. Revisions follow one another, the first fixes the zone, and once a list is live every later one is. A list takes effect strictly after the later of its own instant and every instant earlier records claim. So a list recorded by a clock behind, or after a record stating a later instant, is never backdated: an ask the journal holds before it keeps its standing. Tests: "the channel list is recorded as revisions: …", "KR1's windows freeze at the first live ask: …", "a channel list recorded by a clock behind the ledger's time …" and "a record that states an instant after its recorder's clock …".
- Late records are kept, and no exact figure changes silently (for the same resolved classes). A period reads exact only once it has settled. Within the week after its week, a report is on time and counted as usual. A report made after its week settled is still recorded and counted, and every period holding that week reads "at least": the week itself, and any KR1 window holding it, even one that had not settled when the report arrived. The reason is
late-recordwhen the report's own clock read past the settlement, andclock-disagreementwhen only the ledger's time had. A figure read exact before stays history, not current validity. Tests: "a period settles once the week after it has ended …", "a record that arrives after its own week settled marks every period holding that week, …", "a record made after its week settled is marked even when its own clock calls it on time: …" and "owner-required, parked and live-defaulted items …"; (adversarial) "an ended week must not read exact while the ledger still takes late records …"; (fixture). - Ask Counts are exact only when every listed channel writes the ledger. A period is covered only when a live channel list was in force at its start. Every list in force during it must have every channel declared as writing, and none may have been proved incomplete; the declaration is taken as stated (Trust scope). A rehearsal list in force leaves every Count "at least", or unknown. Tests: "ask Counts are exact only when every listed channel writes …", "a rehearsal list in force leaves every Count …" and "a record through a channel the list in force does not name …"; (adversarial) the rehearsal and unlisted-channel tests.
- A list proved incomplete is incomplete throughout. An ask or update through a channel the list in force does not name marks that revision for its whole time in force. Every week under it reads "at least", including one read exact before; a revision naming the channel ends it. Test: "a record through a channel the list in force does not name …"; (adversarial) "a record through a channel the list does not name …".
- Minutes are exact only when every ask carries minutes, every update its reading time and the weekly note its outside-ask minutes (zero included). An ask's minutes count as settled only once an answer reports them: an open ask carries
unanswered-asks, and an answer without minutesask-minutes-missing. A quiet week with one digest counts that digest's reading time, and an unanswered outside-ask question leaves the week "at least N". Tests: "minutes are exact only when …", "a quiet week with one digest …" and "an unanswered outside-ask question …"; (adversarial) the three minutes tests. - The weekly note is recorded once per ISO week, answered at or after the week's end by its own
answeredAt, and never for a week that ended before the ledger opened. It carries the owner's outside-ask minutes and one tap, both always stated (zero andneitherincluded), counted per period asfeelings; a note without its tap isINVALID. For a week still running on the reader's clock, which only a clock ahead of the reader's can record, the note's figures are "at least" withweek-open. When clocks disagree across the week's end where the note was made, they carryclock-disagreement. Tests: "the weekly note is recorded once per ended week, …", "the owner's do-it-yourself estimate comes with a class's first live answer only, …", "a weekly note recorded by a clock ahead of the reader's …" and "a record that states an instant after its recorder's clock …"; (adversarial round 2) "a weekly note recorded by a clock ahead of the reader's …". - KR1's windows freeze at the first live ask. An ask is live when the list in force when it was asked is live. The first live ask recorded freezes both windows, counted from the week in which it was recorded, so neither starts before the freeze. The baseline is the two full weeks after that week; the evaluation is the two full weeks ending 13 weeks after the baseline ends. Rehearsal asks, later asks, replays and restarts leave the windows as stored. Their dates follow from the freeze's week and from the instant the ledger went live. So both windows carry
clock-disagreementwhen a span holding the freeze's instant reaches across that week's start or end, or a span reaches across the instant the ledger went live; a span between records lasts for good. A span within the freeze's own week leaves the dates standing. Across the autumn clock change the baseline lasts 337 hours; across 2026-W53 and the spring change the evaluation ends 13 of the owner's weeks later. Tests: "KR1's windows freeze …", "KR1's windows follow the owner's calendar …", "KR1's windows are reported apart …", "KR1's windows are counted from the week the first live ask is recorded in, …", "a first live ask recorded by a clock that runs ahead …", "KR1's dates are in doubt only where a span holding the freeze reaches into another week: …" and "a channel list recorded by a clock behind the ledger's time …"; (adversarial) "KR1's dates are frozen when the baseline starts, …". - A period ends, and settles, only on the reader's own clock. A record made by a clock ahead of the reader's ends or settles no week for that reader. Tests: "only the reader's own clock ends a week: …" and "a recorder whose clock reads earlier than the ledger's time …"; (adversarial) "one recorder whose clock runs ahead …"; (adversarial round 2) both tests.
- Rules in the owner's own words. A rule is recorded only with an answer, verbatim, dated by it and scoped to the owner's class and the Products named (or all). It needs a class and an ask whose reserved effects are established and empty, so it is never for a push, a payment, an account or terms. Repeated identical answers never make one. Test: "an owner may attach a rule in their own words …".
- The owner's do-it-yourself estimate comes with an answer that gives a class, and only with the class's first live answer in the journal's order: given once per class, when the class is first asked, before the owner has seen what it costs. A later live answer carrying one is
RULE, so a class whose first live answer gave none has none for good. A rehearsal answer may carry an estimate and a rule, with no standing: the estimate is never the class's and blocks no live one, and the rule's view sayslive: false.classes()lists each class the owner has given, with its live answers, repeats and rules, its estimate and its rehearsal answers apart. Tests: "the owner's do-it-yourself estimate comes with a class's first live answer only, …" and "rehearsal answers carry no standing: …"; (adversarial round 3) "the owner's do-it-yourself estimate is given once per class when the class is first asked: …" and "a rehearsal answer is not the owner's live decision: …"; (fixture). - One plain phrase per reason, and one headline.
REASON_PHRASESnames every reason inLEDGER_REASONS, and the test drives the ledger to each one.headlineReasongives every Count that is not exact one reason, by the order ofLEDGER_REASONS. Tests: "every reason a Count can carry has one plain phrase, …" and "one headline reason per Count, …". - Inputs are bounded and refused, never truncated or repaired, an instant outside 1970 to 9997 and a clock reading past 9997 included. Test: "inputs are bounded and refused, …".
- No record is refused for its clock. A command whose clock reads earlier than an earlier record's claim is recorded at its own instant, and so is one stating an instant after its own
recordedAt; only an answer must still be stated no earlier than its own ask'saskedAt(Failure semantics). Clocks disagree where a recorder's clock reads earlier than an instant an earlier record claims, a record states an instant after its recording, or records claim instants ahead of the reader's clock. A period then reads "at least" or unknown withclock-disagreementonly where the span of disagreement reaches across an instant its figures rest on (What it hides), even once settled. A span wholly inside a period leaves it exact.PeriodReport.disputesandAskView.disputedname the records and recorders. Tests: "a recorder whose clock reads earlier than the ledger's time …", "a record that states an instant after its recorder's clock …", "only the reader's own clock ends a week: …", "a record whose instant is ahead of the reader's clock …", "a first live ask recorded by a clock that runs ahead …" and "KR1's dates are in doubt only where …"; (adversarial round 2) "a recorder whose clock runs 30 days ahead …"; (adversarial round 3) "an ask or update whose own instant reads ahead of its recorder's clock …" and "a clock disagreement lying wholly inside KR1's evaluation window, …". - Corrupt state. Stored state that breaks a rule is
CORRUPTon every read and command, and nothing is written over it. That covers a stored defect, a version no record holds, a first list that is not the first command, a ledger time other than the latest instant a record claims, an estimate with any live answer but its class's first, and a freeze that does not follow from its recording week. Reads check the state only, not the journal's receipts. When a command is executed again, its receipt must hold exactly that command's fields and agree with the record in each of them, a week with the record's own instant, and the record must still hold every field of the request the receipt records. A record whose receipt is missing or disagrees, a record changed in any field its request fixed, or a receipt whose record the state lost, is thenCORRUPT, never replayed or repeated. Tests: "stored state that breaks a rule is corrupt …", "rehearsal answers carry no standing: …", "a record the state holds without its journal receipt, …" and "a replay returns only the record its command made: …". - Bounded state. The command that takes the state to the soft limit is kept and marks the ledger full. Later asks and updates are
LIMIT. Since a late report can then no longer be kept, every period's coverage carriesledger-full, and with it every ask, repeat and minutes Count, including one read exact before; the notes' own figures (outside-ask minutes and taps), which the full ledger still takes, stay as they are. Seventeen weeks of 30 answered asks, 8 updates and a note a week measured 367,281 bytes, against a soft limit of 655,360. Tests: "a full ledger marks itself full, …", "once the ledger is full it can take no more evidence, …" and "state stays bounded over a KR1 horizon: …". - Boundary. The module imports only the five listed and reads time only through the injected clock. Test: "the module reads time only from the injected clock …".
- Fixture replay. Q1's seven prepared owner items, derived from the GitHub runs recorded on 29 September 2026, then the two baseline weeks, give exactly the Counts the records support. A report within the week after its week is counted as usual; one after the baseline settled marks it "at least". Test (fixture): "replaying Q1 and the two baseline weeks …".
Failure semantics
- Input defects are
INVALID, an instant outside 1970 to 9997 included, and so is a clock that cannot be read or reads past 9997. Two times are refused asINVALIDtoo: an ask or update put before the ledger opened, and an answer stated before its ask was put. Count bounds (channels, a rule's Products, resolved classes) and a full ledger areLIMIT. State-dependent refusals areRULE,REVISION,UNKNOWN_ASKorNOT_OPENED.RULEcovers, among others, a rule the ask cannot carry, an estimate with any answer but its class's first live one, and a note for a week not ended by itsansweredAt. None of them writes anything. Lateness is never a refusal, nor is an earlier record's later claim, nor a stated instant after the recorder's clock. - A different request under a recorded command is
CONFLICT. When a command is executed again, a receipt the ledger does not record (even after planning again) or records at another version isCORRUPT, as is a record whose receipt is missing or disagrees with it in any field, or that no longer holds every field of the request its receipt records.VersionConflictErrorre-plans; journal storage errors pass through.
Trust scope
- Established locally (macOS arm64, Node 26.8.1, real SQLite journals in temporary directories, fake clocks): the rules above, including real-process races, the adversary's fifteen tests, mutation checks of the fixed rules (
fix-mutation.log,adv2-mutation.logandr4-mutation.logunder<local evidence archive>) and the fixture replay. - Choices taken here, open to review. The bet leaves these open:
- An ask's minutes belong to the week it was asked; an answer's minutes are the ask's settled total.
- A week settles one week after it ends. A later report for it still counts, and every period holding the week says it is "at least".
- The journal's order is the ledger's sequence, and every instant a record gives is a claim. A disagreement disputes a period only where its span reaches across an instant the period's figures rest on, and a settlement only for the week whose records the span holds. There a disagreement between records lasts for good, since the ledger cannot tell which clock was wrong; a reader's lasts while its clock is behind.
- A list proved incomplete is incomplete for its whole revision.
- A list takes effect strictly after the later of its own instant and every instant earlier records claim. The windows are counted from the week the first live ask is recorded in, and "13 weeks later" in the owner's weeks.
- Rehearsal records are counted as recorded, with
rehearsal, so a rehearsal period is never exact. - The estimate comes with the class's first live answer in the journal's order, and a rehearsal answer's has no standing.
- The order of the headline reasons.
- Channel, class and Product IDs are slugs, and
digestandbatchare the only update kinds.
- Not established. No live ask, channel or owner has been recorded. A channel list's completeness and each channel's
writesare the recorder's statements: a channel never named that never records anything, or a listed one declared as writing that records nothing, is invisible, and the Counts can read exact without it. Nothing is authenticated: anyone who can write the journal can forge an attribution, an answer, a tap or a rule's words. Recorded minutes may be wrong, since self-reports mislead (research.md§5). Subjects and a rule's words are kept as given, within their bounds, so the ledger cannot tell whether they hold a secret or personal data. The blocker classes' resolved classes, power loss and other hosts are not covered. - Clocks. The ledger sees a clock error only when a later record, the reader's clock or a record's own two instants contradict it. Clocks that are wrong together, or a clock ahead whose record nothing contradicts before its instant passes, place records in the wrong week unseen; a note recorded that way before its week truly ended reads as the whole week's. A span of disagreement runs to the nearest records in order on either side, so a disagreement late on a Sunday puts the week's end in doubt when the next record in order comes on Monday. One backwards step of a recorder's clock disputes, for good, every period whose instants its span reaches across, and a freeze made where clocks disagree is never recomputed. A stated instant far ahead of its recorder's clock, such as a wrong year, moves the ledger's time with it, so every later record falls behind and the periods its span reaches across say so until real time passes it. An answer stated before its ask's
askedAtis refused; the ask stays open, and its week's minutes "at least", until one is recorded. - Not built. The digest and exception batches (M6, H2), with their wording of minutes, the stop rule and KR1's target verdicts. Proposing a class to the owner, asking for the estimate with a class's first live answer and defaulting minutes to session time are left to the dashboard. Applying or demoting a rule ("should have asked me"), the list of defaulted decisions and quarterly re-validation (H5). Product deliveries, which KR1 counts from elsewhere. An owner-decided re-freeze of KR1's windows, a Factory-closed state for a withdrawn ask, and per-class minutes over a period for the monthly top three classes, are proposed for the plan's next revision.
- Rollover (not built). The aggregate ID is fixed, so a full ledger stays full: it keeps its records, refuses new asks and updates, and every Count but the notes' own figures says so. The measured 17 weeks, the span from a freeze to the end of KR1's evaluation, use 56% of the soft limit. The proposed procedure is an owner decision before the ledger fills: a successor ledger in a new journal file, opened with a first channel list in the same zone, while KR1's windows are read from the ledger that froze them. Nothing stored under
sf-ask-ledger/1changes for it.
Composition
- Depends on: Command journal, unchanged; three pure zone helpers of the Lifecycle schedule, unchanged; Portfolio's
Counttype. Its journal is meant to be the owner's own (proposal:<local Factory state>); tests use disposable ones. - Used by: nothing. Intelligence declares it
noneinMODULE_INTELLIGENCEwith its one file and does not import it. - Intended callers (not built): the digest and batches record each update with its words and each ask they carry, ask the weekly note, and read
week,kr1,listAsksby Product,REASON_PHRASESandheadlineReason. The dashboard and agents' sessions record asks through listed channels and readclasses()to propose a class. The blocker classes (M5, H4) supply resolved classes; owner-policy's later part (H5) applies and demotes live rules only.
Changing it safely
What must stay true
- A change to a command ID, a stored field, a reason code or a rule's order changes recorded history: stored ledgers must still parse, or a versioned reader (
sf-ask-ledger/2) is added. No ledger outside a test has been stored, so the fields added before review stayed undersf-ask-ledger/1. - The clock stays out of payloads and every rule inside the decision; a refusal writes nothing, and a replay is history, never a second record.
- No command is refused for its clock, or for stating an instant after it, beyond an answer stated before its own ask; lateness and a list's effect are judged in the journal's order.
- No Count reads exact while any category it covers is incomplete or a disagreement the ledger sees reaches across an instant it rests on, and none read exact changes silently for the same resolved classes; unknown is never zero.
- A rehearsal record never stands for the owner: no estimate or rule from rehearsal may count as a live one.
- No path creates or applies a rule outside an owner's answer. Never widen a rule to a reserved effect without a new review.
- The README's intelligence table and this page's intelligence line render from
src/intelligence.ts.
Tests and helpers
Focused tests: node --test test/owner-policy.test.ts test/ask-ledger-adversarial.test.ts test/ask-ledger-r2-adversarial.test.ts test/ask-ledger-r3-adversarial.test.ts test/owner-policy-fixture.test.ts test/owner-policy-processes.test.ts test/intelligence.test.ts. Every change also needs the steps in AGENTS.md's documentation obligations.
| File | Tests | What it covers |
|---|---|---|
test/owner-policy.test.ts | 46 | Channel lists, recording, answering and paging with replay, listing by Product, the five kinds, repeats, coverage, settlement and late records, clock disagreement and where it counts, records stating an instant after their recorder's clock, a list recorded behind, lists proved incomplete, rehearsal, minutes, the weekly note, its tap and a clock ahead, estimates with a class's first live answer, rehearsal's lack of standing and classes, KR1's windows, a freeze from a clock ahead and within its week, the reader's clock, disputed records and recorders, rules, reason phrases and headlines, bounds, corrupt state, missing receipts, replay against the stored record, a full ledger, a 17-week soak and the module's boundary |
test/ask-ledger-adversarial.test.ts | 9 | The independent adversary's first round: open and unreported minutes, late records, a list proved incomplete, a late first live ask, a fast clock, rehearsal weeks and paging |
test/ask-ledger-r2-adversarial.test.ts | 2 | Its second round: a recorder 30 days ahead while correct recorders were refused, and a note from a clock ahead of the reader's |
test/ask-ledger-r3-adversarial.test.ts | 4 | Its third round: records stating an instant ahead of their recorder's clock, an estimate given after the class had been answered, rehearsal estimates and rules, and a disagreement wholly inside KR1's evaluation window |
test/owner-policy-fixture.test.ts | 1 | Q1's owner items from the recorded runs, then two baseline weeks across the autumn clock change, and a report after the baseline settled |
test/owner-policy-processes.test.ts | 3 | Racing recorders and racing answers in separate processes |
test/helpers/owner-policy.ts | helper | Disposable ledgers at a fake instant, request builders, history readers, the autumn 2026 weeks and the Q1 fixture |
test/helpers/owner-policy-child.ts | helper | One recorder process behind a barrier file |
Channel revisions retain strictly increasing journal versions. Other commands may occur between them; a stored reversal is CORRUPT on reads and commands, and records nothing.
Run the local commands. Live use remains separately commissioned.
