Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
9d5141b
roadmap: the dogfood route as one acceptance case (factory-dogfood-ro…
Oct 3, 2026
c55dd72
dogfood route: one record per stage; readings are a product; reuse th…
Oct 3, 2026
c96b593
one segment selector; durable result is the captured submission
Oct 3, 2026
6c9082a
dogfood route: typed per-stage evidence, production order, first-only…
Oct 3, 2026
49f9cdd
dogfood start receipt: total decoder, history scan, one mint from evi…
Oct 3, 2026
b542857
dogfood route: keyed history evidence, candidate before clock, no swe…
Oct 3, 2026
9e52e23
Dogfood G4: return one final, request-bound attempt result on the iss…
Oct 3, 2026
7e9469c
history evidence: one keyed record-path function, witnessed at its in…
Oct 3, 2026
e7e9ca9
Merge remote-tracking branch 'origin/main' into roadmap/dogfood-route
Oct 3, 2026
769dbff
Merge remote-tracking branch 'origin/roadmap/dogfood-route' into sess…
Oct 3, 2026
66d2938
ResultReturned: the recipient is read from the claim, not copied onto…
Oct 3, 2026
529766c
Tick entry: exit semantics as an annotation, not a commentary String row
Oct 3, 2026
845ed0e
dogfood route: provisioned start-receipt root, G5 grant and cleanup c…
Oct 3, 2026
d74ed55
Merge roadmap/dogfood-route (845ed0e5) into the G4 branch
Oct 3, 2026
4bae670
dogfood route: carry G5 evidence to the start receipt, one writer, se…
Oct 3, 2026
12d0a11
Merge remote-tracking branch 'origin/main' into roadmap/dogfood-route
Oct 3, 2026
5b548b0
floor_route_gap: restore chunk 33's closing brace lost in the main merge
Oct 3, 2026
d8a2e26
Merge roadmap/dogfood-route (5b548b04) into the G4 branch
Oct 3, 2026
bca612b
G4, second ruling: claim carried from the dispatch decision, create-o…
Oct 3, 2026
2e2eea8
G4, third ruling: the result writer derives the claim from the attemp…
Oct 4, 2026
98c39ff
History ordering decided on the standing's arms, not its key string
Oct 4, 2026
2dcaefa
Merge origin/main (with #13077 landed) into the G4 branch
Oct 4, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
135 changes: 135 additions & 0 deletions dag/gunbc/roadmap/roadmap_attempt_request_binding.dag
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
module gunbc.roadmap.roadmap_attempt_request_binding

import std.types { String, Bool, List, NonEmptyStr }
import gunbc.roadmap_model { RoadmapNodeId }
import gunbc.roadmap_dashboard_instance { HostDashboardInstance }
import gunbc.roadmap.roadmap_event_log {
RoadmapEventEnvelope, RoadmapEventId, roadmap_claim_return_to, roadmap_history_unordered_reason,
}
import gunbc.roadmap.roadmap_event_carrier {
roadmap_event_carrier_layout_for_instance, roadmap_events_read, RoadmapEventsRead, EventsRead, EventsReadRefused,
}
import gunbc.roadmap.roadmap_attempt_request_record {
AttemptRequestBinding, RequestClaimBound, RequestAutonomous,
AttemptRequestBindingRead, RequestBindingRead, RequestBindingAbsent, RequestBindingUnreadable,
AttemptRequestBindingCommit, RequestBindingCommitted, RequestBindingCommitRefused,
attempt_request_binding_read_for_instance, attempt_request_binding_create_for_instance,
}

// THE REQUEST A LAUNCH WAS DECIDED FOR, CARRIED FROM THE DECISION. An attempt exists because a
// dispatch decision admitted it. When that decision was caused by someone assigning the issue, the
// assignment is one Claimed event on the issue's log, and its id is the request's identity: the
// assign route holds that id the moment the claim is appended and hands it to the dispatch it
// triggers. This type is that hand-off. It is NOT read back from the issue later -- by the time an
// attempt initializes, another claim may be the current one, and binding whichever claim is current
// would address this attempt's result to a request that did not cause it. A dispatch nobody's claim
// caused (the timer's, or an operator pressing dispatch) is Autonomous when it starts a lineage: its
// own launch class, with no requester, never a request whose identity went unobserved.
//
// A CONTINUATION IS NEITHER. When such a dispatch turns out to continue an earlier attempt's
// lineage, that lineage may have a requester, and calling the new turn autonomous would erase the
// obligation -- its result would never return. Continuing names the attempt it continues, and the
// new attempt's binding is a COPY of that attempt's recorded, create-only binding
// (attempt_launch_request_for_origin decides the class; attempt_request_binding_admission does the
// copy). That carries a recorded fact forward along the lineage; it reads no issue state, so the
// claim current at the time of the continuation has no way in.
type AttemptLaunchRequest
= LaunchForClaim { node: RoadmapNodeId, claim: RoadmapEventId }
| LaunchContinuing { predecessor_attempt_key: String }
| LaunchAutonomous

// THE LAUNCH CLASS ONCE THE LINEAGE IS KNOWN. The dispatch decision supplies the request; the
// continuation decision, taken later in the spawn, says whether this attempt continues a
// predecessor. A launch for a claim stays a launch for that claim even when it continues a lineage:
// a new assignment caused it, and it answers that assignment. A launch nobody's claim caused becomes
// Continuing when there is a predecessor and stays Autonomous when there is none. A Continuing
// request is already what it is.
fn attempt_launch_request_for_origin(request: AttemptLaunchRequest, predecessor_attempt_key: String?) -> AttemptLaunchRequest {
match request {
LaunchForClaim { node: _, claim: _ } => request
LaunchContinuing { predecessor_attempt_key: _ } => request
LaunchAutonomous => match predecessor_attempt_key {
Present { value: key } => LaunchContinuing { predecessor_attempt_key: key }
Absent => LaunchAutonomous
}
}
}

// WHETHER THE CARRIED REQUEST CAN BE BOUND TO THIS ATTEMPT, pure over the issue's log and the
// predecessor's binding as read. An autonomous launch binds as autonomous and consults nothing. A
// continuing launch takes exactly the binding its predecessor recorded -- bound to the same claim,
// or autonomous -- and refuses when the predecessor has no readable binding, because a continuation
// whose lineage's request is unknown would run unable to answer anyone (the remedy is a new
// assignment, which launches for its own claim). A launch for a claim binds only when
// the request names THIS attempt's node and the claim is a Claimed event on that node's readable,
// ordered history. Everything else refuses and says which: a request for another node, a log that
// could not be read, a history that forks or is incomplete, a claim id the history does not carry
// as a claim. The log is read to ESTABLISH the claim the decision carried, never to choose one, so
// a newer claim on the same issue changes nothing here.
type AttemptRequestBindingAdmission
= RequestBindingAdmitted { binding: AttemptRequestBinding }
| RequestBindingNotAdmitted { step: String, detail: String }

fn attempt_request_binding_admission(request: AttemptLaunchRequest, node_id: RoadmapNodeId, read: RoadmapEventsRead, predecessor: AttemptRequestBindingRead) -> AttemptRequestBindingAdmission {
match request {
LaunchAutonomous => RequestBindingAdmitted { binding: RequestAutonomous }
LaunchContinuing { predecessor_attempt_key } =>
match predecessor {
RequestBindingRead { binding } => RequestBindingAdmitted { binding: binding }
RequestBindingAbsent =>
RequestBindingNotAdmitted { step: "request-predecessor", detail: join(["this attempt continues attempt ", predecessor_attempt_key, " of issue ", node_id as String, ", which recorded no request binding, so the request this lineage answers is unknown; assign the issue again to continue it under a recorded request"], "") }
RequestBindingUnreadable { reason } =>
RequestBindingNotAdmitted { step: "request-predecessor", detail: join(["this attempt continues attempt ", predecessor_attempt_key, " of issue ", node_id as String, ", whose request binding cannot be read: ", reason], "") }
}
LaunchForClaim { node, claim } =>
if (node as String) != (node_id as String) {
RequestBindingNotAdmitted { step: "request-node", detail: join(["the launch request is for issue ", node as String, " and cannot bind an attempt of issue ", node_id as String], "") }
} else {
match read {
EventsReadRefused { node: _, step, reason } =>
RequestBindingNotAdmitted { step: "request-log", detail: join(["the issue's event log could not be read (", step, "), so the claim this launch answers cannot be established: ", reason], "") }
EventsRead { node: _, envelopes } =>
match roadmap_history_unordered_reason(envs: envelopes) {
Present { value: why } =>
RequestBindingNotAdmitted { step: "request-history", detail: join(["the issue's history cannot be ordered, so the claim this launch answers cannot be established: ", why], "") }
Absent =>
match roadmap_claim_return_to(envs: envelopes, claim: claim) {
Present { value: _ } => RequestBindingAdmitted { binding: RequestClaimBound { claim: claim } }
Absent => RequestBindingNotAdmitted { step: "request-claim", detail: join(["the launch request names claim ", claim as String, ", which is not a claim on issue ", node_id as String, "'s history"], "") }
}
}
}
}
}
}

// THE LAUNCH-TIME COMMIT (gunbc.roadmap_belt_actuate belt_attempt_state_initialize_for_instance,
// before the worker starts). The request arrives from the dispatch decision; the issue's log is
// read only when the request names a claim, to establish it, and the predecessor's binding only
// when the launch continues one (same issue, by the path it is read from); the binding is written
// create-only.
// A refusal here fails the launch.
fn attempt_request_binding_commit_for_instance(instance: HostDashboardInstance, node_id: RoadmapNodeId, attempt_key: String, request: AttemptLaunchRequest) -> AttemptRequestBindingCommit {
let admission = match request {
LaunchAutonomous => attempt_request_binding_admission(request: request, node_id: node_id, read: EventsRead { node: node_id, envelopes: [] }, predecessor: RequestBindingAbsent)
LaunchContinuing { predecessor_attempt_key } =>
attempt_request_binding_admission(
request: request,
node_id: node_id,
read: EventsRead { node: node_id, envelopes: [] },
predecessor: attempt_request_binding_read_for_instance(instance: instance, node_id: node_id, attempt_key: predecessor_attempt_key),
)
LaunchForClaim { node: _, claim: _ } =>
attempt_request_binding_admission(
request: request,
node_id: node_id,
read: roadmap_events_read(layout: roadmap_event_carrier_layout_for_instance(instance: instance), node: node_id),
predecessor: RequestBindingAbsent,
)
}
match admission {
RequestBindingNotAdmitted { step, detail } => RequestBindingCommitRefused { step: step, detail: detail }
RequestBindingAdmitted { binding } =>
attempt_request_binding_create_for_instance(instance: instance, node_id: node_id, attempt_key: attempt_key, binding: binding)
}
}
174 changes: 174 additions & 0 deletions dag/gunbc/roadmap/roadmap_attempt_request_record.dag
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
module gunbc.roadmap.roadmap_attempt_request_record

import std.types { String, Bool, List, NonEmptyStr }
import extdeps.filesystem.filesystem_io {
Filesystem, FilesystemCreateNew, FilesystemCreated, FilesystemCreateTargetOccupied, FilesystemCreateRefused,
FilesystemCreateKindUnrecognized, filesystem_create_new,
}
import extdeps.languages.json.emit { JsonValue, JsonNull, JsonBool, JsonNumber, JsonString, JsonArray, JsonObject, json_object, json_kv, json_string, serialize_json }
import extdeps.languages.json.parse {
parse_json_document, JsonDocumentParsed, JsonDocumentUnreadable, json_document_gap_text,
json_object_unique_member, JsonMemberFound, JsonMemberAbsent, JsonMemberDuplicated, JsonMemberNotAnObject,
}
import gunbc.roadmap_model { RoadmapNodeId }
import gunbc.roadmap_dashboard_instance { HostDashboardInstance }
import gunbc.roadmap_dispatch_actuator { dispatch_attempt_state_path_for_instance }
import gunbc.roadmap.roadmap_event_log { RoadmapEventId }

// THE ATTEMPT'S REQUEST RECORD: the durable, create-only fact of which request an attempt answers.
// It is its own module, below the event carrier, because two readers on opposite sides of that
// carrier need it: the launch (gunbc.roadmap.roadmap_attempt_request_binding), which decides the
// binding with the carrier's help and commits it here, and the carrier's bound result append
// (gunbc.roadmap.roadmap_event_carrier roadmap_bound_result_append), which reads it back to learn
// the claim a result must name -- so that the claim on a result is never something a caller hands
// in. This module knows the record, its codec, its path and its two operations, and nothing about
// events.
// THE BINDING AN ATTEMPT RECORDS, ONCE. Bound names the claim event; Autonomous is an attempt with
// no requester. There is no third state: a request-bound launch whose claim cannot be established
// does not launch (gunbc.roadmap.roadmap_attempt_request_binding attempt_request_binding_commit_for_instance refuses), because an attempt that
// ran without knowing who asked could finish and never be able to answer.
type AttemptRequestBinding
= RequestClaimBound { claim: RoadmapEventId }
| RequestAutonomous

data attempt_request_binding_schema: String = "roadmap-attempt-request-binding/v1"
data attempt_request_binding_basename: String = "request-binding.json"

fn attempt_request_binding_path_for_instance(instance: HostDashboardInstance, node_id: RoadmapNodeId, attempt_key: String) -> String {
join([dispatch_attempt_state_path_for_instance(instance: instance, node_id: node_id, attempt_key: attempt_key), "/", attempt_request_binding_basename], "")
}

fn attempt_request_binding_json(binding: AttemptRequestBinding) -> String {
serialize_json(v: json_object(members: match binding {
RequestClaimBound { claim } => [
json_kv(key: "schema", value: json_string(s: attempt_request_binding_schema)),
json_kv(key: "status", value: json_string(s: "bound")),
json_kv(key: "claim", value: json_string(s: claim as String)),
]
RequestAutonomous => [
json_kv(key: "schema", value: json_string(s: attempt_request_binding_schema)),
json_kv(key: "status", value: json_string(s: "autonomous")),
]
}))
}

fn binding_member_string(doc: JsonValue, key: String) -> String? {
match json_object_unique_member(v: doc, key: key) {
JsonMemberFound { value: JsonString { value: s } } => Present { value: s }
JsonMemberFound { value: JsonNull } => none
JsonMemberFound { value: JsonBool { value: _ } } => none
JsonMemberFound { value: JsonNumber { lexeme: _ } } => none
JsonMemberFound { value: JsonArray { elements: _ } } => none
JsonMemberFound { value: JsonObject { members: _ } } => none
JsonMemberAbsent => none
JsonMemberDuplicated { count: _ } => none
JsonMemberNotAnObject => none
}
}

type AttemptRequestBindingDecode
= RequestBindingDecoded { binding: AttemptRequestBinding }
| RequestBindingUndecodable { reason: String }

// THE DECODE IS STRICT: the schema must be this one, the status one of the two, and a bound record
// must name a non-empty claim. A document that is anything else is undecodable, never autonomous.
fn attempt_request_binding_decode(text: String) -> AttemptRequestBindingDecode {
match parse_json_document(s: text) {
JsonDocumentUnreadable { gap } => RequestBindingUndecodable { reason: join(["not JSON: ", json_document_gap_text(gap: gap)], "") }
JsonDocumentParsed { value: doc } =>
match binding_member_string(doc: doc, key: "schema") {
Absent => RequestBindingUndecodable { reason: "the binding has no schema member" }
Present { value: schema } =>
if schema != attempt_request_binding_schema {
RequestBindingUndecodable { reason: join(["schema is ", schema, ", expected ", attempt_request_binding_schema], "") }
} else {
match binding_member_string(doc: doc, key: "status") {
Absent => RequestBindingUndecodable { reason: "the binding has no status member" }
Present { value: status } =>
if status == "bound" {
match binding_member_string(doc: doc, key: "claim") {
Absent => RequestBindingUndecodable { reason: "a bound binding names no claim" }
Present { value: claim } =>
if claim == "" { RequestBindingUndecodable { reason: "a bound binding names an empty claim" } }
else { RequestBindingDecoded { binding: RequestClaimBound { claim: claim as NonEmptyStr as RoadmapEventId } } }
}
} else if status == "autonomous" {
RequestBindingDecoded { binding: RequestAutonomous }
} else {
RequestBindingUndecodable { reason: join(["status ", status, " is not bound or autonomous"], "") }
}
}
}
}
}
}

// THE CREATE-ONLY COMMIT, pure over what the create answered and what is on disk. Created is the
// first commit. An occupied target is read back: the SAME binding is an idempotent success, so a
// repeated initialization of the same decision changes nothing; a DIFFERENT binding, or a file that
// does not decode, refuses -- the first commit stands and a later initialization under another
// claim cannot replace it. Any other refusal of the create is the host's and fails the launch.
type AttemptRequestBindingCommit
= RequestBindingCommitted { binding: AttemptRequestBinding }
| RequestBindingCommitRefused { step: String, detail: String }

fn attempt_request_binding_commit_of(binding: AttemptRequestBinding, created: FilesystemCreateNew, existing: AttemptRequestBindingRead) -> AttemptRequestBindingCommit {
match created {
FilesystemCreated { path: _ } => RequestBindingCommitted { binding: binding }
FilesystemCreateRefused { path: _, kind: _, error } => RequestBindingCommitRefused { step: "attempt-request-binding-persist", detail: error }
FilesystemCreateKindUnrecognized { path: _, observed, error } => RequestBindingCommitRefused { step: "attempt-request-binding-persist", detail: join([observed, ": ", error], "") }
FilesystemCreateTargetOccupied { path: _ } =>
match existing {
RequestBindingRead { binding: recorded } =>
if recorded == binding { RequestBindingCommitted { binding: recorded } }
else { RequestBindingCommitRefused { step: "attempt-request-binding-conflict", detail: join(["the attempt is already bound (", attempt_request_binding_json(binding: recorded), ") and cannot be re-bound (", attempt_request_binding_json(binding: binding), ")"], "") } }
RequestBindingAbsent => RequestBindingCommitRefused { step: "attempt-request-binding-persist", detail: "the binding could not be created because the target was occupied, and then could not be found" }
RequestBindingUnreadable { reason } => RequestBindingCommitRefused { step: "attempt-request-binding-conflict", detail: join(["the attempt already has a binding that cannot be read, which is not replaced: ", reason], "") }
}
}
}

// THE CREATE-ONLY WRITE: publish the binding with a create that fails when the file exists, then
// decide with attempt_request_binding_commit_of over what the create answered and, only when the
// target was occupied, what is recorded there.
fn attempt_request_binding_create_for_instance(instance: HostDashboardInstance, node_id: RoadmapNodeId, attempt_key: String, binding: AttemptRequestBinding) -> AttemptRequestBindingCommit {
let path = attempt_request_binding_path_for_instance(instance: instance, node_id: node_id, attempt_key: attempt_key)
let write = Filesystem.WriteCreateNew(path: path, content: attempt_request_binding_json(binding: binding))
let created = filesystem_create_new(path: write.path, success: write.success, error: write.error, error_kind: write.error_kind)
attempt_request_binding_commit_of(
binding: binding,
created: created,
existing: match created {
FilesystemCreateTargetOccupied { path: _ } => attempt_request_binding_read_for_instance(instance: instance, node_id: node_id, attempt_key: attempt_key)
FilesystemCreated { path: _ } => RequestBindingAbsent
FilesystemCreateRefused { path: _, kind: _, error: _ } => RequestBindingAbsent
FilesystemCreateKindUnrecognized { path: _, observed: _, error: _ } => RequestBindingAbsent
},
)
}

// THE BINDING AS A LATER READER FINDS IT. Absent is an attempt launched before bindings were
// recorded (or one whose state was never initialized): it has no recorded request, which is its
// own arm and not autonomous. Unreadable is a file that is there and cannot be read or decoded.
type AttemptRequestBindingRead
= RequestBindingRead { binding: AttemptRequestBinding }
| RequestBindingAbsent
| RequestBindingUnreadable { reason: String }

fn attempt_request_binding_read_of(success: Bool, error_kind: String, error: String, content: String) -> AttemptRequestBindingRead {
if success {
match attempt_request_binding_decode(text: content) {
RequestBindingDecoded { binding } => RequestBindingRead { binding: binding }
RequestBindingUndecodable { reason } => RequestBindingUnreadable { reason: reason }
}
} else if error_kind == "not_found" {
RequestBindingAbsent
} else {
RequestBindingUnreadable { reason: error }
}
}

fn attempt_request_binding_read_for_instance(instance: HostDashboardInstance, node_id: RoadmapNodeId, attempt_key: String) -> AttemptRequestBindingRead {
let read = Filesystem.Read(path: attempt_request_binding_path_for_instance(instance: instance, node_id: node_id, attempt_key: attempt_key))
attempt_request_binding_read_of(success: read.success, error_kind: read.error_kind, error: read.error, content: read.content)
}
Loading