CT7: the seat mesh-delivery keeps the state turns, and asks until their last users go; regenerated

Left in the definition: no line of the contracts part removes the state turns, and the
state asks is read by the teller, the forge bridge, the view and the indexer, whose rows
remove their reads; it is removed in the head where its last reader goes (design 47 G §431).

Builds: design 47 G §305, §431.

Claude-Session: https://claude.ai/code/session_01JG73KEq4zoDfM9WtYpcQgZ
This commit is contained in:
2026-11-11 11:05:56 +01:00
parent 3883667254
commit e1df6af536
7 changed files with 386 additions and 18 deletions
@@ -1,18 +0,0 @@
package meshdeliveryclient
// RemovedPrefix opens the description of a verb release 6 removed from the seat mesh-delivery (novox/hq ADR 0539 clause 17):
// the definition keeps it as an optional row with no input, so the holder of before and the holder of release 6 both hold
// the seat, and a caller is answered by name with its replacement.
const RemovedPrefix = "Removed in R6 (ADR 0539): refused by name, naming its replacement: "
// RemovedVerbs are the seat's verbs release 6 removed, each with its replacement in words: with no plan the gate keeps the
// merge's states and derives nothing after the merge. A new map each call.
func RemovedVerbs() map[string]string {
return map[string]string{
"times": "the delivery view (nox-delivery): each machine's want and have, and since when",
"what-if": "the merge check's composition, which composes every machine a change touches",
"release": "the assigner's asks (hold, restore, backup, record), answered on the phone or in the delivery view",
"stop": "a bad fingerprint is held and its canary set back by the assigner (ADR 0429 §6, ADR 0539 clause 18)",
"retire-history": "nothing to retire: the gate keeps no delivery after its merge",
}
}
+117
View File
@@ -102,7 +102,9 @@ const SubjectRecoveryMerged = "mesh.seat.mesh-delivery.event.recovery-merged"
const (
StateSource = "source"
StateMerges = "merges"
StateAsks = "asks"
StateOwed = "owed"
StateTurns = "turns"
)
// SourceByBranchKey is the key of one value of the state source under the form "<branch>".
@@ -167,6 +169,46 @@ func DecodeMergesByGroupValue(raw []byte) (MergesByGroupValue, error) {
return v, nil
}
// AsksByMergeGroupHeadsKey is the key of one value of the state asks under the form "merge.<group>.<heads>".
func AsksByMergeGroupHeadsKey(group, heads string) string { return "merge." + group + "." + heads }
// ParseAsksByMergeGroupHeadsKey reads the parts of a key of the form "merge.<group>.<heads>", in the order of the form; the last result is false for a key of another form.
func ParseAsksByMergeGroupHeadsKey(key string) (string, string, bool) {
rest := key
if !strings.HasPrefix(rest, "merge.") {
return "", "", false
}
rest = rest[6:]
v1, after1, found1 := strings.Cut(rest, ".")
if !found1 || v1 == "" {
return "", "", false
}
rest = after1
if rest == "" {
return "", "", false
}
v3 := rest
rest = ""
if rest != "" {
return "", "", false
}
return v1, v3, true
}
// DecodeAsksByMergeGroupHeadsValue reads one value of the state asks under the form "merge.<group>.<heads>", refusing a field AsksByMergeGroupHeadsValue does not know and any text after the value.
func DecodeAsksByMergeGroupHeadsValue(raw []byte) (AsksByMergeGroupHeadsValue, error) {
var v AsksByMergeGroupHeadsValue
dec := json.NewDecoder(bytes.NewReader(raw))
dec.DisallowUnknownFields()
if err := dec.Decode(&v); err != nil {
return v, err
}
if dec.More() {
return v, errors.New("text after the value")
}
return v, nil
}
// OwedByRepositoryNumberHeadActKey is the key of one value of the state owed under the form "<repository>.<number>.<head>.<act>".
func OwedByRepositoryNumberHeadActKey(repository, number, head, act string) string {
return repository + "." + number + "." + head + "." + act
@@ -215,6 +257,37 @@ func DecodeOwedByRepositoryNumberHeadActValue(raw []byte) (OwedByRepositoryNumbe
return v, nil
}
// TurnsByRepositoryKey is the key of one value of the state turns under the form "<repository>".
func TurnsByRepositoryKey(repository string) string { return repository }
// ParseTurnsByRepositoryKey reads the parts of a key of the form "<repository>", in the order of the form; the last result is false for a key of another form.
func ParseTurnsByRepositoryKey(key string) (string, bool) {
rest := key
if rest == "" {
return "", false
}
v0 := rest
rest = ""
if rest != "" {
return "", false
}
return v0, true
}
// DecodeTurnsByRepositoryValue reads one value of the state turns under the form "<repository>", refusing a field TurnsByRepositoryValue does not know and any text after the value.
func DecodeTurnsByRepositoryValue(raw []byte) (TurnsByRepositoryValue, error) {
var v TurnsByRepositoryValue
dec := json.NewDecoder(bytes.NewReader(raw))
dec.DisallowUnknownFields()
if err := dec.Decode(&v); err != nil {
return v, err
}
if dec.More() {
return v, errors.New("text after the value")
}
return v, nil
}
// Act is an act the gate said for a group, with its recorded outcome once the forge bridge said one.
type Act struct {
// when the outcome was recorded
@@ -595,6 +668,27 @@ type MergesByGroupValue struct {
V int64 `json:"v,omitempty"`
}
// AsksByMergeGroupHeadsValueHeadsItem is a type of the contract.
type AsksByMergeGroupHeadsValueHeadsItem struct {
// the full commit
Head string `json:"head"`
Number int64 `json:"number"`
// <owner>/<name>
Repository string `json:"repository"`
}
// AsksByMergeGroupHeadsValue is one value of the state asks under the key merge.<group>.<heads>.
type AsksByMergeGroupHeadsValue struct {
// the operator-channel ask as raised
Ask any `json:"ask"`
// the number of times it was asked
Asking int64 `json:"asking,omitempty"`
Group string `json:"group"`
Heads []AsksByMergeGroupHeadsValueHeadsItem `json:"heads"`
// the layout version
V int64 `json:"v,omitempty"`
}
// OwedByRepositoryNumberHeadActValue is one value of the state owed under the key <repository>.<number>.<head>.<act>.
type OwedByRepositoryNumberHeadActValue struct {
// status=<context>, view, note or comment.<seq>
@@ -607,3 +701,26 @@ type OwedByRepositoryNumberHeadActValue struct {
// the layout version
V int64 `json:"v,omitempty"`
}
// TurnsByRepositoryValueWaitingItem is a type of the contract.
type TurnsByRepositoryValueWaitingItem struct {
// when (RFC 3339 time)
Approved time.Time `json:"approved"`
Ask string `json:"ask,omitempty"`
Group string `json:"group"`
Heads string `json:"heads"`
// when (RFC 3339 time)
Since time.Time `json:"since"`
Step string `json:"step,omitempty"`
WaitsFor string `json:"waits-for,omitempty"`
// when (RFC 3339 time)
WaitsSince *time.Time `json:"waits-since,omitempty"`
}
// TurnsByRepositoryValue is one value of the state turns under the key <repository>.
type TurnsByRepositoryValue struct {
Repository string `json:"repository"`
// the layout version
V int64 `json:"v,omitempty"`
Waiting []TurnsByRepositoryValueWaitingItem `json:"waiting"`
}
@@ -53,6 +53,14 @@ func TestFixtures(t *testing.T) {
err := json.Unmarshal(b, &v)
return v, err
}, subjectIs: nil},
{name: "a turn", subject: "", given: "{\"repository\":\"novox/mesh-sdk\",\"v\":1,\"waiting\":[{\"approved\":\"2026-11-05T10:00:00Z\",\"group\":\"feat/registry\",\"heads\":\"0123456789ab\",\"since\":\"2026-11-05T10:01:00Z\"}]}", wire: "{\"repository\":\"novox/mesh-sdk\",\"v\":1,\"waiting\":[{\"approved\":\"2026-11-05T10:00:00Z\",\"group\":\"feat/registry\",\"heads\":\"0123456789ab\",\"since\":\"2026-11-05T10:01:00Z\"}]}",
decode: func(b []byte) (any, error) { var v TurnsByRepositoryValue; err := json.Unmarshal(b, &v); return v, err }, subjectIs: nil},
{name: "an ask", subject: "", given: "{\"ask\":{\"headline\":\"merge\"},\"group\":\"feat/registry\",\"heads\":[{\"head\":\"cccccccccccccccccccccccccccccccccccccccc\",\"number\":1,\"repository\":\"novox/mesh-sdk\"}],\"v\":1}", wire: "{\"ask\":{\"headline\":\"merge\"},\"group\":\"feat/registry\",\"heads\":[{\"head\":\"cccccccccccccccccccccccccccccccccccccccc\",\"number\":1,\"repository\":\"novox/mesh-sdk\"}],\"v\":1}",
decode: func(b []byte) (any, error) {
var v AsksByMergeGroupHeadsValue
err := json.Unmarshal(b, &v)
return v, err
}, subjectIs: nil},
{name: "groups asks for one group", subject: "", given: "{\"group\":\"feat/47-G\"}", wire: "{\"group\":\"feat/47-G\"}",
decode: func(b []byte) (any, error) { var v GroupsInput; err := json.Unmarshal(b, &v); return v, err }, subjectIs: nil},
{name: "groups answers a group with its members and acts", subject: "", given: "{\"groups\":[{\"acts\":[{\"at\":\"2026-11-09T20:01:00Z\",\"outcome\":\"performed\",\"owed\":{\"act\":\"merge\",\"ask\":\"merge.feat=47-G.0123456789ab\",\"group\":\"feat/47-G\",\"head\":\"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\",\"id\":\"merge.feat=47-G.0123456789ab.novox/mesh-sdk#178\",\"number\":178,\"repository\":\"novox/mesh-sdk\",\"said\":\"2026-11-09T20:00:00Z\"}},{\"owed\":{\"act\":\"status\",\"context\":\"mesh/merge-gate\",\"description\":\"waits for the operator's Approve\",\"head\":\"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\",\"id\":\"status.novox/mesh-sdk#178.aaaaaaaaaaaa.mesh/merge-gate\",\"number\":178,\"repository\":\"novox/mesh-sdk\",\"state\":\"pending\",\"target\":\"https://forge.test/x\"}}],\"check\":\"pass\",\"id\":\"feat/47-G\",\"members\":[{\"base\":\"main\",\"head\":\"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\",\"id\":\"novox/mesh-sdk#178\",\"number\":178,\"open\":true,\"repository\":\"novox/mesh-sdk\",\"state\":\"merging\"}],\"state\":\"merging\",\"why\":\"novox/mesh-sdk#178 merges first\"}]}", wire: "{\"groups\":[{\"acts\":[{\"at\":\"2026-11-09T20:01:00Z\",\"outcome\":\"performed\",\"owed\":{\"act\":\"merge\",\"ask\":\"merge.feat=47-G.0123456789ab\",\"group\":\"feat/47-G\",\"head\":\"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\",\"id\":\"merge.feat=47-G.0123456789ab.novox/mesh-sdk#178\",\"number\":178,\"repository\":\"novox/mesh-sdk\",\"said\":\"2026-11-09T20:00:00Z\"}},{\"owed\":{\"act\":\"status\",\"context\":\"mesh/merge-gate\",\"description\":\"waits for the operator's Approve\",\"head\":\"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\",\"id\":\"status.novox/mesh-sdk#178.aaaaaaaaaaaa.mesh/merge-gate\",\"number\":178,\"repository\":\"novox/mesh-sdk\",\"state\":\"pending\",\"target\":\"https://forge.test/x\"}}],\"check\":\"pass\",\"id\":\"feat/47-G\",\"members\":[{\"base\":\"main\",\"head\":\"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\",\"id\":\"novox/mesh-sdk#178\",\"number\":178,\"open\":true,\"repository\":\"novox/mesh-sdk\",\"state\":\"merging\"}],\"state\":\"merging\",\"why\":\"novox/mesh-sdk#178 merges first\"}]}",
@@ -34,5 +34,39 @@
"v": 1,
"act": "view"
}
},
{
"name": "a turn",
"of": "state:turns/<repository>",
"given": {
"v": 1,
"repository": "novox/mesh-sdk",
"waiting": [
{
"group": "feat/registry",
"heads": "0123456789ab",
"approved": "2026-11-05T10:00:00Z",
"since": "2026-11-05T10:01:00Z"
}
]
}
},
{
"name": "an ask",
"of": "state:asks/merge.<group>.<heads>",
"given": {
"v": 1,
"group": "feat/registry",
"heads": [
{
"repository": "novox/mesh-sdk",
"number": 1,
"head": "cccccccccccccccccccccccccccccccccccccccc"
}
],
"ask": {
"headline": "merge"
}
}
}
]
+133
View File
@@ -567,6 +567,66 @@
"description": "the merges",
"history": 10
},
{
"name": "asks",
"template": "state",
"key-forms": [
{
"key": "merge.<group>.<heads>",
"value": {
"type": "object",
"properties": {
"v": {
"type": "integer",
"description": "the layout version"
},
"group": {
"type": "string"
},
"heads": {
"type": "array",
"items": {
"type": "object",
"properties": {
"repository": {
"type": "string",
"description": "<owner>/<name>"
},
"number": {
"type": "integer"
},
"head": {
"type": "string",
"description": "the full commit"
}
},
"required": [
"repository",
"number",
"head"
]
}
},
"ask": {
"description": "the operator-channel ask as raised"
},
"asking": {
"type": "integer",
"description": "the number of times it was asked"
}
},
"required": [
"group",
"heads",
"ask"
]
},
"description": "the ask of the operator for one group at one set of heads"
}
],
"description": "the asks of the operator for a merge",
"history": 5
},
{
"name": "owed",
"template": "state",
@@ -610,6 +670,79 @@
],
"description": "the acts the gate owes the forge",
"history": 5
},
{
"name": "turns",
"template": "state",
"key-forms": [
{
"key": "<repository>",
"value": {
"type": "object",
"properties": {
"v": {
"type": "integer",
"description": "the layout version"
},
"repository": {
"type": "string"
},
"waiting": {
"type": "array",
"items": {
"type": "object",
"properties": {
"group": {
"type": "string"
},
"heads": {
"type": "string"
},
"ask": {
"type": "string"
},
"approved": {
"type": "string",
"format": "date-time",
"description": "when (RFC 3339 time)"
},
"since": {
"type": "string",
"format": "date-time",
"description": "when (RFC 3339 time)"
},
"waits-for": {
"type": "string"
},
"waits-since": {
"type": "string",
"format": "date-time",
"description": "when (RFC 3339 time)"
},
"step": {
"type": "string"
}
},
"required": [
"group",
"heads",
"approved",
"since"
]
}
}
},
"required": [
"repository",
"waiting"
],
"description": "the merges of a repository in the order they wait for their turn"
},
"description": "the merges waiting on a repository"
}
],
"description": "which merge waits for which",
"history": 5
}
],
"defs": {
+56
View File
@@ -60,7 +60,9 @@ export declare const recoveryMergedSubject: "mesh.seat.mesh-delivery.event.recov
export declare const states: {
readonly source: "source";
readonly merges: "merges";
readonly asks: "asks";
readonly owed: "owed";
readonly turns: "turns";
};
/** The key of one value of the state source under the form "<branch>". */
@@ -75,12 +77,24 @@ export declare function mergesByGroupKey(group: string): string;
/** The parts of a key of the form "<group>", or undefined for a key of another form. */
export declare function parseMergesByGroupKey(_key: string): { group: string } | undefined;
/** The key of one value of the state asks under the form "merge.<group>.<heads>". */
export declare function asksByMergeGroupHeadsKey(group: string, heads: string): string;
/** The parts of a key of the form "merge.<group>.<heads>", or undefined for a key of another form. */
export declare function parseAsksByMergeGroupHeadsKey(_key: string): { group: string; heads: string } | undefined;
/** The key of one value of the state owed under the form "<repository>.<number>.<head>.<act>". */
export declare function owedByRepositoryNumberHeadActKey(repository: string, number: string, head: string, act: string): string;
/** The parts of a key of the form "<repository>.<number>.<head>.<act>", or undefined for a key of another form. */
export declare function parseOwedByRepositoryNumberHeadActKey(_key: string): { repository: string; number: string; head: string; act: string } | undefined;
/** The key of one value of the state turns under the form "<repository>". */
export declare function turnsByRepositoryKey(repository: string): string;
/** The parts of a key of the form "<repository>", or undefined for a key of another form. */
export declare function parseTurnsByRepositoryKey(_key: string): { repository: string } | undefined;
/** an act the gate said for a group, with its recorded outcome once the forge bridge said one */
export interface Act {
/** when the outcome was recorded */
@@ -429,6 +443,26 @@ export interface MergesByGroupValue {
"v"?: number;
}
export interface AsksByMergeGroupHeadsValueHeadsItem {
/** the full commit */
"head": string;
"number": number;
/** <owner>/<name> */
"repository": string;
}
/** is one value of the state asks under the key merge.<group>.<heads>. */
export interface AsksByMergeGroupHeadsValue {
/** the operator-channel ask as raised */
"ask": unknown;
/** the number of times it was asked */
"asking"?: number;
"group": string;
"heads": AsksByMergeGroupHeadsValueHeadsItem[];
/** the layout version */
"v"?: number;
}
/** is one value of the state owed under the key <repository>.<number>.<head>.<act>. */
export interface OwedByRepositoryNumberHeadActValue {
/** status=<context>, view, note or comment.<seq> */
@@ -442,3 +476,25 @@ export interface OwedByRepositoryNumberHeadActValue {
"v"?: number;
}
export interface TurnsByRepositoryValueWaitingItem {
/** when (RFC 3339 time) */
"approved": string;
"ask"?: string;
"group": string;
"heads": string;
/** when (RFC 3339 time) */
"since": string;
"step"?: string;
"waits-for"?: string;
/** when (RFC 3339 time) */
"waits-since"?: string;
}
/** is one value of the state turns under the key <repository>. */
export interface TurnsByRepositoryValue {
"repository": string;
/** the layout version */
"v"?: number;
"waiting": TurnsByRepositoryValueWaitingItem[];
}
+38
View File
@@ -64,7 +64,9 @@ export const recoveryMergedSubject = "mesh.seat.mesh-delivery.event.recovery-mer
export const states = {
source: "source",
merges: "merges",
asks: "asks",
owed: "owed",
turns: "turns",
};
/** The key of one value of the state source under the form "<branch>". */
@@ -97,6 +99,27 @@ export function parseMergesByGroupKey(_key) {
return { group };
}
/** The key of one value of the state asks under the form "merge.<group>.<heads>". */
export function asksByMergeGroupHeadsKey(group, heads) {
return "merge." + group + "." + heads;
}
/** The parts of a key of the form "merge.<group>.<heads>", or undefined for a key of another form. */
export function parseAsksByMergeGroupHeadsKey(_key) {
let _rest = _key;
if (!_rest.startsWith("merge.")) return undefined;
_rest = _rest.slice(6);
const _at1 = _rest.indexOf(".");
if (_at1 <= 0) return undefined;
const group = _rest.slice(0, _at1);
_rest = _rest.slice(_at1 + 1);
if (_rest === "") return undefined;
const heads = _rest;
_rest = "";
if (_rest !== "") return undefined;
return { group, heads };
}
/** The key of one value of the state owed under the form "<repository>.<number>.<head>.<act>". */
export function owedByRepositoryNumberHeadActKey(repository, number, head, act) {
return repository + "." + number + "." + head + "." + act;
@@ -124,3 +147,18 @@ export function parseOwedByRepositoryNumberHeadActKey(_key) {
return { repository, number, head, act };
}
/** The key of one value of the state turns under the form "<repository>". */
export function turnsByRepositoryKey(repository) {
return repository;
}
/** The parts of a key of the form "<repository>", or undefined for a key of another form. */
export function parseTurnsByRepositoryKey(_key) {
let _rest = _key;
if (_rest === "") return undefined;
const repository = _rest;
_rest = "";
if (_rest !== "") return undefined;
return { repository };
}