Skip to content

Commit 3767916

Browse files
docs(archive): session record for sustainabot #148 hand-port validation (#209)
## Summary Adds the human-readable session report and its machine-readable companion for the 2026-05-26 sustainabot ReScript→AffineScript hand-port validation work (Refs #148). - `docs/archive/SESSION-2026-05-26-sustainabot-148-validation.md` — human-readable session record. Goal, approach (parser-fix vs hand-port-rewrite decision rule), the 5 affinescript parser PRs + 1 gitbot-fleet hand-port PR, validation-oracle output, gotchas surfaced (HANDLE-as-fn-name, no-OCaml-float-ops), and out-of-scope follow-ups (#208). - `docs/archive/README.md` — table updated with the new entry. - `.machine_readable/SESSION-2026-05-26-148.a2ml` — structured companion. PR list with branch names + scopes, before/after validation counts, CI-status notes (incl. baseline noise), captured gotcha rules, next-gate identification, downstream work that unblocks after #148. The work itself ships in the PR queue (no source-file changes here). Issue #148 closure is owner-merge gated per the `ISSUE-CLOSURE` rule. ## Cross-refs - Refs #148, #206, #208 - Refs hyperpolymath/affinescript#370, #371, #372, #373, #376 ## Test plan - [x] Markdown renders correctly on GitHub preview - [x] A2ML file follows existing structure in `.machine_readable/STATE.a2ml` - [x] `docs/archive/README.md` table updated 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 4e5d525 commit 3767916

3 files changed

Lines changed: 268 additions & 0 deletions

File tree

Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
# SPDX-License-Identifier: MPL-2.0
2+
# Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) <j.d.a.jewell@open.ac.uk>
3+
#
4+
# SESSION-2026-05-26-148.a2ml — sustainabot ReScript→AffineScript hand-port
5+
# validation session record. Machine-readable companion to
6+
# `docs/archive/SESSION-2026-05-26-sustainabot-148-validation.md`.
7+
8+
[metadata]
9+
session-id = "2026-05-26-sustainabot-148"
10+
date = "2026-05-26T08:30:00Z"
11+
agent = "claude-opus-4-7"
12+
agent-context = "1M"
13+
issue = "hyperpolymath/gitbot-fleet#148"
14+
duration-hours = 1.5
15+
status = "complete-pending-owner-merge"
16+
17+
[goal]
18+
description = """Reduce every parse error in the 13 sustainabot .affine
19+
files (4,939 LOC hand-ported from .res) to either type-check or
20+
Resolve.UndefinedModule. The latter is the acceptable residual (stdlib
21+
graph not loaded by single-file `check`; INT-02 territory)."""
22+
23+
[scope.in]
24+
paths = [
25+
"bots/sustainabot/bot-integration/src/Analysis.affine",
26+
"bots/sustainabot/bot-integration/src/Config.affine",
27+
"bots/sustainabot/bot-integration/src/GitHubAPI.affine",
28+
"bots/sustainabot/bot-integration/src/GitHubApp.affine",
29+
"bots/sustainabot/bot-integration/src/Main.affine",
30+
"bots/sustainabot/bot-integration/src/Oikos.affine",
31+
"bots/sustainabot/bot-integration/src/Report.affine",
32+
"bots/sustainabot/bot-integration/src/Router.affine",
33+
"bots/sustainabot/bot-integration/src/Types.affine",
34+
"bots/sustainabot/bot-integration/src/Webhook.affine",
35+
"bots/sustainabot/bot-integration/src/tea/Cmd.affine",
36+
"bots/sustainabot/bot-integration/src/tea/Runtime.affine",
37+
"bots/sustainabot/bot-integration/src/tea/Sub.affine"
38+
]
39+
file-count = 13
40+
41+
[scope.out]
42+
note = """SafeDOMExample.affine fixtures in 3 other bots
43+
(the-hotchocolabot, echidnabot, finishingbot) use a pre-stabilization
44+
AffineScript dialect (8+ grammar divergences). Tracked separately at
45+
gitbot-fleet#208; needs single dialect-migration PR, NOT parser
46+
changes."""
47+
filed-issue = "hyperpolymath/gitbot-fleet#208"
48+
49+
[validation-oracle]
50+
command = "affinescript check <path>"
51+
pass-criteria = "parse layer succeeds — `Type checking passed` OR `Resolution error: (Resolve.UndefinedModule …)`"
52+
fail-criteria = "any line starting with `<path>:<line>:<col>: parse error: Syntax error`"
53+
54+
[validation-oracle.before-session]
55+
files-passing = 0
56+
files-with-resolution-residual = 1 # tea/Sub.affine — only file simple enough to reach Resolution unaided
57+
files-with-parse-errors = 12
58+
59+
[validation-oracle.after-session]
60+
files-passing = 0 # full typecheck not in scope — stdlib not loaded
61+
files-with-resolution-residual = 13
62+
files-with-parse-errors = 0
63+
64+
[parser-prs]
65+
repo = "hyperpolymath/affinescript"
66+
count = 5
67+
conflict-cost-total = "+0 S/R, +0 R/R (parser builds at 21 S/R + 1 R/R, baseline unchanged)"
68+
prs = [
69+
{ number = 370, branch = "claude/parser-trailing-comma-148", scope = "trailing-comma in fn params + expr lists; effect-annotated lambda" },
70+
{ number = 371, branch = "claude/parser-fn-type-eff-arrow-148", scope = "fn-type with effect arrow `fn(A) -{E}-> B` in type position" },
71+
{ number = 372, branch = "claude/parser-builtin-qualified-paths-148", scope = "builtin/lowercase qualified paths + TOTAL field name" },
72+
{ number = 373, branch = "claude/lexer-underscore-idents-148", scope = "underscore-prefix idents `_key`/`_unused`" },
73+
{ number = 376, branch = "claude/parser-record-spread-148", scope = "record-update spread at start `#{ ..base, f: v }`" }
74+
]
75+
76+
[handport-prs]
77+
repo = "hyperpolymath/gitbot-fleet"
78+
count = 1
79+
prs = [
80+
{ number = 206, branch = "claude/sustainabot-parse-fixes-148", scope = "OCaml-style float ops → unified ops; `fn handle` → `fn dispatch` (HANDLE is a keyword)" }
81+
]
82+
83+
[ci-status]
84+
all-prs-real-signal = "green"
85+
known-baseline-noise = [
86+
"vscode-smoke (affinescript) — fails on every PR; npm 404 on @hyperpolymath/affine-vscode (affinescript#104)",
87+
"governance / Language / package anti-pattern policy (affinescript) — flags approved TypeScript exemptions"
88+
]
89+
90+
[gotchas-captured]
91+
description = """Rules surfaced during the session that apply to ALL
92+
future .affine hand-ports (not just sustainabot). Captured in agent
93+
persistent memory."""
94+
rules = [
95+
{ name = "handle-is-keyword", body = "`handle` is HANDLE token, parse-fails as fn name. Rename to `dispatch`/`handle_request` on hand-port.", scope = "hand-port-rewrite" },
96+
{ name = "no-ocaml-float-ops", body = "`+. -. *. /.` never accepted. Mechanical hand-port-rewrite to unified `+ - * /`, never parser-fix.", scope = "hand-port-rewrite" }
97+
]
98+
99+
[next-gate]
100+
type = "owner-merge"
101+
description = """All 6 PRs CI-green and merge-ready. Issue #148 is NOT
102+
auto-closed by any `Refs` keyword (ISSUE-CLOSURE rule). Owner-merge
103+
of the 6 PRs is the prerequisite to closing #148."""
104+
105+
[downstream-blocked]
106+
description = "What unblocks after #148 closes (next-gate state):"
107+
items = [
108+
"Resolution → typecheck cross-module loader work (INT-02 in affinescript docs/TECH-DEBT.adoc)",
109+
"Bigger .res → .affine sweep via tree-sitter walker (affinescript#57 Phase 2)"
110+
]
111+
112+
[artefacts]
113+
human-readable = "docs/archive/SESSION-2026-05-26-sustainabot-148-validation.md"
114+
issue-thread-comment = "https://github.com/hyperpolymath/gitbot-fleet/issues/148#issuecomment-4542225835"
115+
116+
[session.signature]
117+
agent = "claude-opus-4-7"
118+
verified-by = "validation-oracle re-run on integrated branch with all 6 PRs cherry-picked"
119+
verified-at = "2026-05-26T09:30:00Z"

docs/archive/README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ evolution and are kept for audit and change-context tracing.
1414
| `FLEET-DEPLOYMENT-STATUS.md` | 2026-02-06 | Deployment status snapshot at the operational-handoff point. |
1515
| `NEW-PATTERNS-DEPLOYMENT-2026-02-06.md` | 2026-02-06 | Rollout report for 4 new Hypatia patterns across 14 supervised repos. |
1616
| `OUTSTANDING-WORK.md` | 2026-02-07 | Consolidated open-work list across the fleet ecosystem (later superseded by `READINESS.md` + per-bot `ROADMAP.adoc`s). |
17+
| `SESSION-2026-05-26-sustainabot-148-validation.md` | 2026-05-26 | sustainabot ReScript→AffineScript hand-port validation for #148: 6 PRs land all 13 `.affine` files at Resolution; conflict-neutral parser work + hand-port rewrites; gotchas for future hand-ports. |
1718

1819
For the current state, see the repo-root `README.adoc`, `ROADMAP.adoc`,
1920
and `READINESS.md`.
Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
<!-- SPDX-License-Identifier: MPL-2.0 -->
2+
<!-- SPDX-FileCopyrightText: 2026 Jonathan D.A. Jewell -->
3+
4+
# sustainabot ReScript→AffineScript hand-port validation
5+
6+
**Date**: 2026-05-26
7+
**Agent**: Claude Opus 4.7 (1M context)
8+
**Session**: Issue #148 — gitbot-fleet/bots/sustainabot/bot-integration/src `.affine` parse validation
9+
**Status**: Validation complete; 6 PRs filed; gates on owner-merge
10+
11+
---
12+
13+
## Goal
14+
15+
Run `affinescript check` on the 13 hand-ported `.affine` files under
16+
`bots/sustainabot/bot-integration/src/` and reduce every parse error to
17+
either a successful type-check or to `Resolve.UndefinedModule` (the
18+
expected residual when single-file `check` doesn't load the stdlib
19+
graph — INT-02 loader-bridge territory, out of scope here).
20+
21+
The 13 files (a 4,939 LOC migration from the original `.res` set):
22+
23+
```
24+
src/Analysis.affine src/Main.affine src/Router.affine src/tea/Sub.affine
25+
src/Config.affine src/Oikos.affine src/Types.affine
26+
src/GitHubAPI.affine src/Report.affine src/Webhook.affine
27+
src/GitHubApp.affine src/tea/Cmd.affine
28+
src/tea/Runtime.affine
29+
```
30+
31+
## Approach
32+
33+
For each parse failure encountered, decide **parser-fix** (upstream
34+
`hyperpolymath/affinescript`, `lib/parser.mly`) vs **hand-port-rewrite**
35+
(this repo, the `.affine` source). Default:
36+
37+
* **parser-fix** when the failing syntax is documented as part of the
38+
AffineScript language surface (ADR-008/009 / SETTLED-DECISIONS in the
39+
affinescript repo) and dropping it would create a gap.
40+
* **hand-port-rewrite** when the syntax is an OCaml/ReScript-ism the
41+
language never promised.
42+
43+
Constraint on every parser-fix: zero new LR conflicts. Baseline is
44+
**21 shift/reduce + 1 reduce/reduce**; verified after every patch via
45+
`menhir --explain`.
46+
47+
## Outcome
48+
49+
All 13 files now reach **Resolution** (parser layer fully clear). The
50+
work bundled into 6 PRs across 2 repos:
51+
52+
### Parser PRs (hyperpolymath/affinescript)
53+
54+
| PR | Scope | Branch |
55+
|---|---|---|
56+
| [#370](https://github.com/hyperpolymath/affinescript/pull/370) | Trailing-comma in fn params + expr lists; effect-annotated lambda `fn() -{IO}-> M { … }` | `claude/parser-trailing-comma-148` |
57+
| [#371](https://github.com/hyperpolymath/affinescript/pull/371) | fn-type with effect arrow `fn(A, B) -{E}-> R` in type position | `claude/parser-fn-type-eff-arrow-148` |
58+
| [#372](https://github.com/hyperpolymath/affinescript/pull/372) | Builtin-type qualified paths (`Int::to_string`); lowercase-module qualified paths (`json::encode_object`); `total` as a record field name | `claude/parser-builtin-qualified-paths-148` |
59+
| [#373](https://github.com/hyperpolymath/affinescript/pull/373) | Underscore-prefix idents `_key`, `_unused` lex as a single LOWER_IDENT (bare `_` still lexes as UNDERSCORE) | `claude/lexer-underscore-idents-148` |
60+
| [#376](https://github.com/hyperpolymath/affinescript/pull/376) | Record-update spread at start `Record #{ ..base, override: x }` | `claude/parser-record-spread-148` |
61+
62+
### Hand-port PR (hyperpolymath/gitbot-fleet)
63+
64+
| PR | Scope | Branch |
65+
|---|---|---|
66+
| [#206](https://github.com/hyperpolymath/gitbot-fleet/pull/206) | OCaml-style float ops `/.`, `*.`, `+.`, `-.` → unified AffineScript `/`, `*`, `+`, `-`; `pub fn handle(...)``pub fn dispatch(...)` (HANDLE is a reserved keyword) | `claude/sustainabot-parse-fixes-148` |
67+
68+
## Validation oracle (post-merge)
69+
70+
```
71+
$ for f in bots/sustainabot/bot-integration/src/*.affine \
72+
bots/sustainabot/bot-integration/src/tea/*.affine; do
73+
affinescript check "$f"
74+
done
75+
76+
Analysis.affine Resolution error: (Resolve.UndefinedModule …
77+
Config.affine Resolution error: (Resolve.UndefinedModule …
78+
GitHubAPI.affine Resolution error: (Resolve.UndefinedModule …
79+
GitHubApp.affine Resolution error: (Resolve.UndefinedModule …
80+
Main.affine Resolution error: (Resolve.UndefinedModule …
81+
Oikos.affine Resolution error: (Resolve.UndefinedModule …
82+
Report.affine Resolution error: (Resolve.UndefinedModule …
83+
Router.affine Resolution error: (Resolve.UndefinedModule …
84+
tea/Cmd.affine Resolution error: (Resolve.UndefinedModule …
85+
tea/Runtime.affine Resolution error: (Resolve.UndefinedModule …
86+
tea/Sub.affine Resolution error: (Resolve.UndefinedModule …
87+
Types.affine Resolution error: (Resolve.UndefinedModule …
88+
Webhook.affine Resolution error: (Resolve.UndefinedModule …
89+
```
90+
91+
`Resolve.UndefinedModule` is the expected residual: the cross-module
92+
loader is tracked separately under INT-02 in
93+
`hyperpolymath/affinescript` `docs/TECH-DEBT.adoc`. Closing that gap is
94+
a different effort, not within #148.
95+
96+
## Gotchas discovered (captured for future hand-ports)
97+
98+
Two hand-port-rewrite rules surfaced that are worth documenting up-front
99+
for the broader `.res → .affine` walk (affinescript#57):
100+
101+
1. **`handle` is a reserved keyword** in AffineScript (HANDLE token,
102+
used by the effect-handler expression form
103+
`handle body { handler_arms }`). It parses as a token, not an
104+
identifier, so `pub fn handle(...)` is a syntax error. The
105+
`field_name` rule allows `handle` contextually as a record field
106+
name (the surrounding COLON disambiguates), but no equivalent is
107+
safe in fn-decl name position without grammar conflict risk.
108+
*Workaround*: rename to `dispatch`, `handle_request`,
109+
`handle_event`, etc.
110+
111+
2. **No OCaml-style float operators.** AffineScript uses unified `+`,
112+
`-`, `*`, `/` for both Int and Float (per
113+
`examples/lessons/01_hello.affine`: `subtotal * 0.08`). `+.`, `-.`,
114+
`*.`, `/.` are never accepted and must be rewritten on port. This
115+
is a hand-port-rewrite, not a parser-fix candidate — adding the
116+
OCaml form would create operator overlap for no semantic benefit.
117+
118+
Both are recorded in the agent's persistent memory for the next session
119+
and are also captured as `Refs gitbot-fleet#148` commits in #206.
120+
121+
## Out of scope (filed separately)
122+
123+
3 byte-identical `SafeDOMExample.affine` fixtures
124+
(`bots/{the-hotchocolabot,echidnabot,finishingbot}/examples/`) parse-
125+
fail with a different shape: they use a **pre-stabilization AffineScript
126+
dialect** with 8+ grammar divergences from current. Tracked at
127+
[gitbot-fleet#208](https://github.com/hyperpolymath/gitbot-fleet/issues/208).
128+
Recommended disposition: a single dialect-migration PR (or deletion if
129+
the examples are unreferenced — `grep -r SafeDOMExample` will say).
130+
131+
## Conflict-cost on the parser
132+
133+
All 5 upstream parser PRs together net **zero new LR conflicts**: the
134+
parser builds at 21 S/R + 1 R/R, identical to the pre-patch baseline.
135+
The conflict-neutrality discipline is required by ADR-012 (Grammar
136+
Changes Are Correctness Assertions) in the affinescript repo.
137+
138+
## Status comment on the tracking issue
139+
140+
A consolidated status post is on [gitbot-fleet#148](https://github.com/hyperpolymath/gitbot-fleet/issues/148#issuecomment-4542225835)
141+
linking each of the six PRs, showing the validation oracle output, and
142+
noting INT-02 as the next gate. The issue is **not** auto-closed by
143+
any `Refs` keyword — that's an owner-merge action per the
144+
`ISSUE-CLOSURE` rule in the repo's CLAUDE.md.
145+
146+
---
147+
148+
*Generated 2026-05-26 by Claude Opus 4.7 (1M context).*

0 commit comments

Comments
 (0)