Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
19 changes: 19 additions & 0 deletions .agents/skills/open-pr/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
---
name: open-pr
description: "Open the pull request for the current branch with a Boatstack-structured description, gated on a verified self-review attestation."
---

<!-- generated-by: yskill; source: skills/open-pr; digest: sha256:49be3144bb174f090f3a88c64c20bce631722c3446b2e70fa6e75b076c817b02; version: 0.1.38 -->

This adapter exposes the canonical Yield workflow at `skills/open-pr`.
Read its SKILL.md, then run from the repository root:

.yield/bin/yskill run 'skills/open-pr'

Follow each returned operation exactly. Answer each operation directly:

.yield/bin/yskill respond <run-id> --value <answer> --skill 'skills/open-pr'

For structured agent results, use --result-json instead of --value.

Do not skip an operation or invent its response.
2 changes: 1 addition & 1 deletion .agents/skills/self-review-solve/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ name: self-review-solve
description: "Resolve the Boatstack self-review: fix open findings or run a fresh review, converge the loop, and seal the receipt."
---

<!-- generated-by: yskill; source: skills/self-review-solve; digest: sha256:08304ccee8618eb8dbad3cc2185a13fb53c2732ec64252e6e963c0f9678ec5e0; version: 0.1.38 -->
<!-- generated-by: yskill; source: skills/self-review-solve; digest: sha256:7a1cceee94acc7fcd5ab1388478e84ea3160d57c873e6a1515464a6a9437df1d; version: 0.1.38 -->

This adapter exposes the canonical Yield workflow at `skills/self-review-solve`.
Read its SKILL.md, then run from the repository root:
Expand Down
2 changes: 1 addition & 1 deletion .agents/skills/self-review/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ name: self-review
description: "Run the Boatstack supervisory-control self-review for the current branch and report the verdict without changing code."
---

<!-- generated-by: yskill; source: skills/self-review; digest: sha256:2d1b51f55e65418d692f21c3263f8ec1f11660937366ee5a141ce2e2fed79edb; version: 0.1.38 -->
<!-- generated-by: yskill; source: skills/self-review; digest: sha256:7be36f40fd15f0ada56cec3378fb2b9a17ced46b5ec256182879f1b9fa12cdda; version: 0.1.38 -->

This adapter exposes the canonical Yield workflow at `skills/self-review`.
Read its SKILL.md, then run from the repository root:
Expand Down
19 changes: 19 additions & 0 deletions .claude/skills/open-pr/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
---
name: open-pr
description: "Open the pull request for the current branch with a Boatstack-structured description, gated on a verified self-review attestation."
---

<!-- generated-by: yskill; source: skills/open-pr; digest: sha256:49be3144bb174f090f3a88c64c20bce631722c3446b2e70fa6e75b076c817b02; version: 0.1.38 -->

This adapter exposes the canonical Yield workflow at `skills/open-pr`.
Read its SKILL.md, then run from the repository root:

.yield/bin/yskill run 'skills/open-pr'

Follow each returned operation exactly. Answer each operation directly:

.yield/bin/yskill respond <run-id> --value <answer> --skill 'skills/open-pr'

For structured agent results, use --result-json instead of --value.

Do not skip an operation or invent its response.
2 changes: 1 addition & 1 deletion .claude/skills/self-review-solve/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ name: self-review-solve
description: "Resolve the Boatstack self-review: fix open findings or run a fresh review, converge the loop, and seal the receipt."
---

<!-- generated-by: yskill; source: skills/self-review-solve; digest: sha256:08304ccee8618eb8dbad3cc2185a13fb53c2732ec64252e6e963c0f9678ec5e0; version: 0.1.38 -->
<!-- generated-by: yskill; source: skills/self-review-solve; digest: sha256:7a1cceee94acc7fcd5ab1388478e84ea3160d57c873e6a1515464a6a9437df1d; version: 0.1.38 -->

This adapter exposes the canonical Yield workflow at `skills/self-review-solve`.
Read its SKILL.md, then run from the repository root:
Expand Down
2 changes: 1 addition & 1 deletion .claude/skills/self-review/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ name: self-review
description: "Run the Boatstack supervisory-control self-review for the current branch and report the verdict without changing code."
---

<!-- generated-by: yskill; source: skills/self-review; digest: sha256:2d1b51f55e65418d692f21c3263f8ec1f11660937366ee5a141ce2e2fed79edb; version: 0.1.38 -->
<!-- generated-by: yskill; source: skills/self-review; digest: sha256:7be36f40fd15f0ada56cec3378fb2b9a17ced46b5ec256182879f1b9fa12cdda; version: 0.1.38 -->

This adapter exposes the canonical Yield workflow at `skills/self-review`.
Read its SKILL.md, then run from the repository root:
Expand Down
19 changes: 19 additions & 0 deletions .cursor/skills/open-pr/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
---
name: open-pr
description: "Open the pull request for the current branch with a Boatstack-structured description, gated on a verified self-review attestation."
---

<!-- generated-by: yskill; source: skills/open-pr; digest: sha256:49be3144bb174f090f3a88c64c20bce631722c3446b2e70fa6e75b076c817b02; version: 0.1.38 -->

This adapter exposes the canonical Yield workflow at `skills/open-pr`.
Read its SKILL.md, then run from the repository root:

.yield/bin/yskill run 'skills/open-pr'

Follow each returned operation exactly. Answer each operation directly:

.yield/bin/yskill respond <run-id> --value <answer> --skill 'skills/open-pr'

For structured agent results, use --result-json instead of --value.

Do not skip an operation or invent its response.
2 changes: 1 addition & 1 deletion .cursor/skills/self-review-solve/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ name: self-review-solve
description: "Resolve the Boatstack self-review: fix open findings or run a fresh review, converge the loop, and seal the receipt."
---

<!-- generated-by: yskill; source: skills/self-review-solve; digest: sha256:08304ccee8618eb8dbad3cc2185a13fb53c2732ec64252e6e963c0f9678ec5e0; version: 0.1.38 -->
<!-- generated-by: yskill; source: skills/self-review-solve; digest: sha256:7a1cceee94acc7fcd5ab1388478e84ea3160d57c873e6a1515464a6a9437df1d; version: 0.1.38 -->

This adapter exposes the canonical Yield workflow at `skills/self-review-solve`.
Read its SKILL.md, then run from the repository root:
Expand Down
2 changes: 1 addition & 1 deletion .cursor/skills/self-review/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ name: self-review
description: "Run the Boatstack supervisory-control self-review for the current branch and report the verdict without changing code."
---

<!-- generated-by: yskill; source: skills/self-review; digest: sha256:2d1b51f55e65418d692f21c3263f8ec1f11660937366ee5a141ce2e2fed79edb; version: 0.1.38 -->
<!-- generated-by: yskill; source: skills/self-review; digest: sha256:7be36f40fd15f0ada56cec3378fb2b9a17ced46b5ec256182879f1b9fa12cdda; version: 0.1.38 -->

This adapter exposes the canonical Yield workflow at `skills/self-review`.
Read its SKILL.md, then run from the repository root:
Expand Down
27 changes: 26 additions & 1 deletion .github/codex/review-prompt.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ Do not report:
* architectural alternatives that are merely cleaner;
* issues whose location or impact cannot be established from available evidence.

If you discover an important pre-existing issue near the changed surface, record it as a carried note inside `overall_explanation`, clearly labeled as pre-existing. Never emit it as a finding, and never let it affect the verdict.

The important review principle is:

Local correctness does not imply control-system correctness.
Expand Down Expand Up @@ -276,6 +278,24 @@ Boatstack kernel

A repository control program must not be able to reach around the declared program interface and mutate kernel semantics directly.

## Durable-contract deltas

Apply the deep checks in this section only when the diff itself changes a durable-contract surface:

* a schema version, durable file format, or receipt/journal shape;
* an authority, capability, or identity rule;
* a recovery mapping or failure-to-transition routing;
* an admission, freshness, or compare-and-swap boundary.

When the diff touches such a surface, check the deployed-world cases, not just the code's internal consistency:

* migration from the schema/format that is actually deployed, not only from empty state;
* recovery reachability from states real deployments occupy, including states written by the previous version;
* authority bound to the exact issuance and exact target it was granted for;
* the window between resolve and apply under the changed freshness rule.

When the diff does not touch such a surface, do not hunt these classes. A small change gets a small review scoped to what it changed.

## Review to closure before reporting findings

Do not return as soon as you find the first valid counterexample.
Expand Down Expand Up @@ -388,13 +408,18 @@ Verdict:
or
* "patch is incorrect"

The verdict is decided by the blocking boundary, not by the presence of findings:

* "patch is incorrect" is reserved for reviews with at least one blocking finding (priority P0 or P1);
* a review whose findings are all P2 or P3 returns "patch is correct" — those findings are residuals, recorded with the review but not blocking it.

Then provide:

* a concise explanation;
* confidence from 0 to 1;
* whether model-level verification is recommended before merge.

"Patch is correct" means no actionable defect introduced by this change was established from the available evidence.
"Patch is correct" means no blocking defect introduced by this change was established from the available evidence; residual P2/P3 findings may still be listed.

It does not mean global liveness or formal correctness has been proven.

Expand Down
4 changes: 4 additions & 0 deletions .github/reviews/convergence-boundary.receipt.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"reviewed_tree": "638048ded4504ed3df431637e803348cc2100340",
"program_fingerprint": "ddbd77f1cbc842b3dffcb8e54b53ddcc624a998fb9c5a9ace15028580ab87967"
}
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@ boatstack/boatstack-reviewer.exe
.yield/
skills/*/.yield-runs/
skills/*/fixtures/tmp/
skills/self-review/self-review
skills/self-review-solve/self-review-solve
skills/open-pr/open-pr
dist/
build/docs/
node_modules/
Expand Down
37 changes: 26 additions & 11 deletions boatstack/cmd/boatstack-reviewer/candidate.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,14 +51,15 @@ type reviewAnchor struct {
// observation from the exact staged bytes and the exact current diff; the
// proposer's own claims are never trusted.
type candidateSummary struct {
Fingerprint string `json:"fingerprint"`
ReviewedTree string `json:"reviewed_tree"`
Valid bool `json:"valid"`
InvalidReasons []string `json:"invalid_reasons,omitempty"`
Verdict string `json:"verdict,omitempty"`
Measure int `json:"measure"`
FindingCount int `json:"finding_count"`
Priorities [4]int `json:"priorities"`
Fingerprint string `json:"fingerprint"`
ReviewedTree string `json:"reviewed_tree"`
Valid bool `json:"valid"`
InvalidReasons []string `json:"invalid_reasons,omitempty"`
Verdict string `json:"verdict,omitempty"`
Measure int `json:"measure"`
BlockingMeasure int `json:"blocking_measure"`
FindingCount int `json:"finding_count"`
Priorities [4]int `json:"priorities"`
}

// candidateFingerprint identifies candidate review bytes by the sha256 of
Expand Down Expand Up @@ -119,10 +120,21 @@ func evaluateCandidate(policy Policy, candidateBytes []byte, stagedTree, repoRoo
summary.FindingCount = len(review.Findings)
allowed := changedLines(diff)
valid := true
// An incorrect verdict must be backed by at least one concrete finding;
// an empty assertion of incorrectness carries nothing the loop could
// act on and nothing the record could bind.
if review.OverallCorrectness == verdictIncorrect && len(review.Findings) == 0 {
valid = false
summary.InvalidReasons = append(summary.InvalidReasons,
"verdict rejects the patch but the review carries no findings")
}
for index, finding := range review.Findings {
if finding.Priority >= 0 && finding.Priority <= 3 {
summary.Priorities[finding.Priority]++
summary.Measure += policy.Weights[finding.Priority]
if policy.Blocking[finding.Priority] {
summary.BlockingMeasure += policy.Weights[finding.Priority]
}
}
if reason := anchorFailure(finding.CodeLocation, repoRoot, allowed); reason != "" {
valid = false
Expand Down Expand Up @@ -264,9 +276,12 @@ func mustInt(value string) int {

// stalled reports whether recording this candidate would extend the trailing
// run of submissions without measure improvement to the policy stall window.
// The run length counts the submissions themselves: with a window of three,
// a third consecutive submission that fails to decrease the measure below
// its predecessor escalates instead of recording another round.
// The measures compared are BLOCKING measures: only progress on blocking
// findings counts, so residual P2/P3 churn can neither mask a stall nor
// trigger one. The run length counts the submissions themselves: with a
// window of three, a third consecutive submission that fails to decrease
// the blocking measure below its predecessor escalates instead of
// recording another round.
func stalled(policy Policy, recorded []int, candidateMeasure int) bool {
if len(recorded) == 0 {
return false
Expand Down
40 changes: 32 additions & 8 deletions boatstack/cmd/boatstack-reviewer/domain.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,14 @@ type observationValue struct {
Rounds []journalRound `json:"rounds"`
}

func (v observationValue) roundMeasures() []int {
// roundBlockingMeasures projects the recorded rounds onto the convergence
// law's driving quantity: the blocking measure. Round and stall bounds are
// computed over this sequence, so residual (non-blocking) findings can
// neither extend nor cut short a review generation.
func (v observationValue) roundBlockingMeasures() []int {
measures := make([]int, 0, len(v.Rounds))
for _, round := range v.Rounds {
measures = append(measures, round.Measure)
measures = append(measures, round.BlockingMeasure)
}
return measures
}
Expand Down Expand Up @@ -143,17 +147,35 @@ func submissionDisposition(policy Policy, observed observationValue) (string, st
}
return "", reasons
}
if candidate.Verdict == verdictCorrect {
return transitionConverge, "candidate verdict accepts the patch"
// Convergence is deterministic on the blocking boundary: zero open
// blocking findings converges regardless of the verdict wording, and
// any open blocking finding keeps the loop running regardless of it.
// The verdict and residual (non-blocking) findings are recorded data.
if candidate.BlockingMeasure == 0 {
if residuals := residualCount(policy, candidate.Priorities); residuals > 0 {
return transitionConverge, fmt.Sprintf("no blocking findings remain; %d residual non-blocking findings are recorded", residuals)
}
return transitionConverge, "no blocking findings remain"
}
rounds := observed.roundMeasures()
rounds := observed.roundBlockingMeasures()
if len(rounds) >= policy.MaxRounds {
return transitionEscalate, fmt.Sprintf("round bound %d is exhausted", policy.MaxRounds)
}
if stalled(policy, rounds, candidate.Measure) {
return transitionEscalate, fmt.Sprintf("measure has not decreased for %d consecutive submissions", policy.StallWindow)
if stalled(policy, rounds, candidate.BlockingMeasure) {
return transitionEscalate, fmt.Sprintf("blocking measure has not decreased for %d consecutive submissions", policy.StallWindow)
}
return transitionRecord, "candidate records open blocking findings with a decreasing blocking measure"
}

// residualCount counts findings in non-blocking priority classes.
func residualCount(policy Policy, priorities [4]int) int {
count := 0
for priority, findings := range priorities {
if !policy.Blocking[priority] {
count += findings
}
}
return transitionRecord, "candidate records open findings with a decreasing measure"
return count
}

// reviewOperator applies the one admitted operation. It receives only
Expand Down Expand Up @@ -187,6 +209,7 @@ func (o reviewOperator) Execute(_ context.Context, operation kernel.Operation) (
MergeBase: observed.MergeBase,
Verdict: observed.Candidate.Verdict,
Measure: observed.Candidate.Measure,
BlockingMeasure: observed.Candidate.BlockingMeasure,
FindingCount: observed.Candidate.FindingCount,
Priorities: observed.Candidate.Priorities,
Transition: operation.Transition.ID,
Expand Down Expand Up @@ -252,6 +275,7 @@ func (d *reviewDomain) Verify(_ context.Context, evaluation kernel.Evaluation, e
recorded.ReviewedTree != before.ReviewedTree ||
recorded.Verdict != before.Candidate.Verdict ||
recorded.Measure != before.Candidate.Measure ||
recorded.BlockingMeasure != before.Candidate.BlockingMeasure ||
recorded.Transition != evaluation.Transition.ID {
return fmt.Errorf("recorded round does not match the admitted candidate")
}
Expand Down
30 changes: 27 additions & 3 deletions boatstack/cmd/boatstack-reviewer/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,12 @@ func newLoopContext(repoPath, delivery, baseRef string) (*loopContext, error) {
if err != nil {
return nil, err
}
policy, err := loadWorktreePolicy(repo.Root)
// The policy is admitted from the base revision — the same admission CI
// verification performs. A branch that changes the policy assets is
// therefore reviewed under the currently-admitted policy, and the
// changed policy governs only after it merges; otherwise the sealed
// attestation would carry a program fingerprint CI can never recompute.
policy, err := loadRevisionPolicy(repo, baseRef)
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -152,6 +157,24 @@ type instructionsView struct {
SchemaPath string `json:"output_schema_path"`
SchemaSHA256 string `json:"output_schema_sha256"`
SubmitCommand string `json:"submit_command"`
PolicyNote string `json:"policy_note,omitempty"`
}

// worktreePolicyNote reports when the worktree policy assets differ from the
// base-admitted ones, so a proposer reading the worktree files knows which
// bytes govern the program identity and candidate validation.
func worktreePolicyNote(repoRoot string, admitted Policy) string {
for _, asset := range []struct{ path, admittedHash string }{
{policyPromptPath, admitted.PromptSHA256},
{policySchemaPath, admitted.SchemaSHA256},
} {
contents, err := os.ReadFile(filepath.Join(repoRoot, filepath.FromSlash(asset.path)))
if err != nil || sha256Hex(contents) != asset.admittedHash {
return "the worktree policy assets differ from the base-admitted policy; " +
"the admitted (base revision) assets govern the program identity and candidate validation"
}
}
return ""
}

func commandResolve(arguments []string) error {
Expand Down Expand Up @@ -215,6 +238,7 @@ func commandResolve(arguments []string) error {
SchemaPath: policySchemaPath,
SchemaSHA256: loop.policy.SchemaSHA256,
SubmitCommand: "boatstack-reviewer submit --findings <path> --actor <name>",
PolicyNote: worktreePolicyNote(loop.repo.Root, loop.policy),
},
})
}
Expand Down Expand Up @@ -302,9 +326,9 @@ func commandSubmit(arguments []string) error {
func submissionGuidance(mode string) string {
switch mode {
case modeConverged:
return "review converged; run `boatstack-reviewer seal` and commit the sealed receipt"
return "review converged; run `boatstack-reviewer seal` and commit the sealed attestation"
case modeFindingsOpen:
return "findings are open; fix them, commit, and submit a fresh review of the new tree"
return "blocking findings are open; fix them, commit, and submit a fresh review of the new tree"
case modeEscalated:
return "the loop escalated; a human must decide, then `boatstack-reviewer reopen --actor <name>`"
default:
Expand Down
Loading
Loading