Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
44 commits
Select commit Hold shift + click to select a range
a5a1e2b
docs: logic spec for data-state examples on logic-spec scenarios
zpyoung Oct 12, 2026
1a39a23
docs: approve scenario data-state logic spec, request tech spec; add …
zpyoung Oct 12, 2026
e039c3a
docs: tech spec for scenario data-state examples
zpyoung Oct 12, 2026
1e5d0ea
docs: record authored tech spec in logic spec status
zpyoung Oct 12, 2026
1c6ce66
test: logic.md goldens guard renderer output for specs without examples
zpyoung Oct 12, 2026
78c1b53
feat(writing-specs): validate, gate and render scenario data-state ex…
zpyoung Oct 12, 2026
e20f75b
Merge branch 'sdd/scenario-examples-T1' into zpyoung/scenario-data-st…
zpyoung Oct 12, 2026
17289a7
feat(writing-specs): show scenario examples in the review page focus …
zpyoung Oct 12, 2026
eb7669a
Merge branch 'sdd/scenario-examples-T2' into zpyoung/scenario-data-st…
zpyoung Oct 12, 2026
ce6ef00
docs(writing-specs): scenario example conventions and rubric rules
zpyoung Oct 12, 2026
6985fb0
Merge branch 'sdd/scenario-examples-T3' into zpyoung/scenario-data-st…
zpyoung Oct 12, 2026
d363552
Merge branch 'sdd/scenario-examples-T4' into zpyoung/scenario-data-st…
zpyoung Oct 12, 2026
d190f9f
docs: tech spec treats a blank custom outcome as unpicked for the aut…
zpyoung Oct 12, 2026
0a8752b
fix(writing-specs): keep invented-value stars literal in the example …
zpyoung Oct 12, 2026
0a506be
fix(writing-specs): one examples gate failure per scenario
zpyoung Oct 12, 2026
be1a112
Merge branch 'sdd/scenario-examples-fix-F1' into zpyoung/scenario-dat…
zpyoung Oct 12, 2026
5ad4228
Merge branch 'sdd/scenario-examples-fix-F2' into zpyoung/scenario-dat…
zpyoung Oct 12, 2026
904556d
docs: sync tech spec with the round-1 review fixes
zpyoung Oct 12, 2026
7f42bce
fix(writing-specs): linear [ASK] detection and invented values inside…
zpyoung Oct 12, 2026
aad3ef8
Merge branch 'sdd/scenario-examples-fix-F3' into zpyoung/scenario-dat…
zpyoung Oct 12, 2026
1948860
docs: sync tech spec with the round-2 review fixes
zpyoung Oct 12, 2026
4330710
fix(writing-specs): colour whole result phrases and anchor invented-v…
zpyoung Oct 12, 2026
bb9bc22
Merge branch 'sdd/scenario-examples-fix-F4' into zpyoung/scenario-dat…
zpyoung Oct 12, 2026
23691d0
docs: sync tech spec with the round-3 review fixes
zpyoung Oct 12, 2026
8de4349
chore: log BUG-12 (pi-watch breaks on pi-coding-agent 1.1.0)
zpyoung Oct 12, 2026
88a0b53
docs: tech spec describes phrase splitting and the page link guard
zpyoung Oct 12, 2026
647decc
fix(writing-specs): render only safe link schemes in page markdown; k…
zpyoung Oct 12, 2026
251276f
Merge branch 'sdd/scenario-examples-fix-F5' into zpyoung/scenario-dat…
zpyoung Oct 12, 2026
be4a5f2
fix(writing-specs): mark adjacent comma-separated invented values; br…
zpyoung Oct 15, 2026
78ebb05
chore: log BUG-13 (Astryx markdown parser quadratic on unmatched '[')
zpyoung Oct 15, 2026
6df4756
chore: remove BUG-12 from BUGS.md
zpyoung Oct 15, 2026
f7408d8
fix(writing-specs): cap logic.json text fields so the review page's m…
zpyoung Oct 15, 2026
52147f6
docs: amend scenario examples logic spec with titled details sections
zpyoung Oct 15, 2026
051ad93
docs: tech spec contracts for scenario details sections
zpyoung Oct 15, 2026
a0bc13b
feat(writing-specs): validate and render example details sections
zpyoung Oct 15, 2026
13af0d3
feat(writing-specs): show example details sections in the focus dialog
zpyoung Oct 15, 2026
36f6712
Merge branch 'sdd/details-T1' into zpyoung/scenario-data-states
zpyoung Oct 15, 2026
18befea
Merge branch 'sdd/details-T2' into zpyoung/scenario-data-states
zpyoung Oct 15, 2026
cea8303
docs(writing-specs): conventions and rubric rules for example details…
zpyoung Oct 15, 2026
e246148
Merge branch 'sdd/details-T3' into zpyoung/scenario-data-states
zpyoung Oct 15, 2026
4de844d
chore: log BUG-14 (SpecMarkdown underscore guard breaks link URLs)
zpyoung Oct 15, 2026
68ccc55
fix(writing-specs): keep code fences in list items, escaped backticks…
zpyoung Oct 15, 2026
c0552de
Merge branch 'sdd/details-fix-F1' into zpyoung/scenario-data-states
zpyoung Oct 15, 2026
fe2e038
Merge branch 'main' of https://github.com/zpyoung/quirk into zpyoung/…
zpyoung Oct 15, 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
17 changes: 17 additions & 0 deletions BUGS.md
Original file line number Diff line number Diff line change
Expand Up @@ -116,3 +116,20 @@ entries' IDs; manual edits to fix typos are fine.
- **Proposed fix**: After session.prompt(), check the last assistant message's stopReason; on error, write errorMessage to stderr and exit non-zero.
- **Resolved**: 2026-10-15. After session.prompt(), pi-watch reads the last assistant message the way pi's print mode does: stopReason error or aborted exits 1 with errorMessage on stderr, and length exits 1 as truncated. The check runs before --require-trailer, so a provider failure no longer surfaces as exit 6. BUG-9 (--check passing an expired token) stays open.

## BUG-13: Review page markdown renders in quadratic time on long runs of unmatched '['
- **Observed**: 2026-10-15
- **File**: skills/writing-specs/interactive/app/src/views/scenario-example.tsx:51
- **Description**: The Astryx 0.6.2 Markdown parser is quadratic on long runs of unmatched '['. It affects every markdown field on the writing-specs review page, examples included, through SpecMarkdown in src/views/shared.tsx too. Timings: 100k '[' 92ms, 200k 334ms, 400k 1301ms, 800k 5172ms. Found by the round-5 security review of scenario-data-states. Triggering it takes a hostile 400k+ character spec field, and spec JSON is Claude-written.
- **Introduced by**: upstream @astryxdesign/core 0.6.2 Markdown parser (pre-dates scenario-data-states)
- **Severity**: low
- **Proposed fix**: Bound example and markdown field length in spec validation, or escape pathological unmatched delimiters before parsing.
- **Resolved**: 2026-10-15. render_spec.py validation now rejects any logic.json string over MAX_TEXT_CHARS (10,000), reported at its JSON pointer. The parser cost is per paragraph, so the cap bounds every render site (SpecMarkdown, the example panes, and the generated logic.md in SpecView) to about 24ms per field in the worst case. The parser itself is unchanged.

## BUG-14: Review page links lose their underscores' meaning: SpecMarkdown inserts zero-width spaces into link URLs
- **Observed**: 2026-10-15
- **File**: skills/writing-specs/interactive/app/src/views/shared.tsx:37
- **Description**: SpecMarkdown's page-wide underscore guard (children.replace(/_/g, '_\u200b')) also rewrites Markdown link destinations, so [r](https://x.test/a_b) renders href="https://x.test/a_\u200bb" and the link opens the wrong URL. Affects every SpecMarkdown field on the review page: explanations, details, custom views. Found by the round-1 correctness review of the details sections, which fixed the same defect in scenario-example.tsx; shared.tsx is fenced in that work.
- **Introduced by**: pre-dates the details-section work (SpecMarkdown underscore guard)
- **Severity**: medium
- **Proposed fix**: Skip link destinations and autolinks when applying the underscore guard, as scenario-example.tsx now does.

88 changes: 88 additions & 0 deletions docs/quirk/explorations/2026-10-12-scenario-data-states.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
> 🧭 EXPLORATION — not a spec. No locked decisions; nothing here is build-ready.

# Exploring: data-state examples for logic-spec scenarios

**Date**: 2026-10-12

## Framing

A logic-spec scenario states a rule as Given / When / Then. That's precise enough for the person who wrote it, but a reader still has to picture the records and run the numbers in their head. A data-state example does that work for them: it shows the records before the scenario, each step with its intermediate values, and the records after.

This came out of a real review. After a subscription-discount logic spec was approved, its 68 scenarios were exported to a spreadsheet and three data-state columns were added by hand. Writing them surfaced four questions the approved Given / When / Then had skipped over. One example: once a "first 3 charges" discount runs out, does it stay attached to the subscription or get removed? Reviewers also found the examples much faster to check than the prose.

The question for brainstorming is whether, and how, the logic-spec flow should produce these examples itself.

## What a data-state example is

Three parts, attached to one scenario:

- **Data before:** the records the scenario touches, with only the values that matter to it.
- **Walkthrough:** numbered steps from the When, showing each intermediate value.
- **Data after:** the same records once the scenario finishes. This is the Then written as data.

### Example: two percentage discounts compound

Scenario: *Given a $100 line with a 10% and a 15% line discount, when the charge is calculated, then the line nets $76.50.*

| Data before | Walkthrough | Data after |
|---|---|---|
| **Charge 1**<br>Coffee line: $100.00, quantity 1<br>Line discount "Loyalty": 10% off<br>Line discount "Winback": 15% off | 1. "Loyalty": $100.00 × 0.90 = $90.00<br>2. "Winback": $90.00 × 0.85 = $76.50 | **Charge 1**<br>Coffee line: $76.50<br>"Loyalty" took $10.00<br>"Winback" took $13.50 |

### Example: a limited discount runs out

Scenario: *Given a subscription with a line discount limited to the first 3 charges, when three charges have processed and the next one is generated, then the fourth charge doesn't include it.*

| Data before | Walkthrough | Data after |
|---|---|---|
| **Subscription**<br>Line discount "Welcome": 10%* off, first 3 charges<br>Uses left: 3 | 1. Charge 1 processes → uses left 2<br>2. Charge 2 processes → uses left 1<br>3. Charge 3 processes → uses left 0<br>4. Charge 4 is generated | **Charge 4**: Coffee line has no line discounts<br>**Subscription**: "Welcome" uses left 0<br>[ASK: does "Welcome" stay on the subscription once its uses run out, or get removed?] |

### Example: one scenario, two branches

Scenario: *Given a shared discount definition at 10%, when the merchant edits it to 15%, then the edit is ignored once the definition has been used, and allowed while it hasn't.*

| Data before | Walkthrough | Data after |
|---|---|---|
| **Discount definition** "Loyalty": 10% off<br>(a) used 4* times<br>(b) never used | 1. The merchant changes "Loyalty" to 15%<br>(a) The request succeeds, but the change is ignored<br>(b) The change is saved | (a) "Loyalty" stays 10%<br>(b) "Loyalty" is 15%; queued charges pick it up on their next regen |

## Conventions that made them readable

The first draft used IDs (`C1 line A D1 10% amount 4.00`). Reviewers found it hard to follow, so the second draft switched to these conventions:

- **Records are named in full:** "Subscription", "Charge 2 (queued)", "Discount definition".
- **Lines are named after a product:** "Coffee line", not "line A".
- **Discounts have titles:** "Loyalty", "Sorry", "Welcome". The same title is reused for the same kind of discount across scenarios.
- **Every number has a label:** `"Winback": $90.00 × 0.85 = $76.50` and `"Loyalty" took $10.00`, not bare figures.
- **Steps say who acts:** "The customer skips Charge 2", "An API client tries to add…".
- **Invented numbers are marked `*`.** Where the scenario doesn't give a value, one is picked to make the example concrete, and the mark says it isn't part of the rule. Product names and discount titles are always illustrative.
- **Branches are labelled (a), (b)** when one scenario covers several outcomes.
- **`[ASK: …]` marks a gap,** a detail the scenario doesn't settle that the example can't show without guessing.
- **Not every scenario changes data over time.** For API-shape, webhook and feature-flag scenarios, the walkthrough is the request and *Data after* is the response or payload.

In the spreadsheet, colour carried part of the meaning: records in blue bold, step numbers in purple, results in green bold, rejections in red, illustrative values in grey italic, and `[ASK]` in underlined amber. *Data before* had a grey background and *Data after* a green one.

## What they were good for

- **Finding gaps.** Writing *Data after* forces a concrete answer. Four of the 68 scenarios had a question the prose hadn't answered:
- what happens to an expired discount;
- what happens to a discount left off because the currency changed;
- the format of a "created by" field;
- a timing mechanism that had been deferred.
- **Checking arithmetic.** Calculation scenarios (stacking, rounding, clamping, tax bases, currency conversion) became easy to verify line by line.
- **Onboarding readers.** People who weren't in the review could follow a scenario without the surrounding prose.

## Costs and risks seen

- **Invented values can read as spec.** The `*` mark and the "names are illustrative" note are what keep this from happening. Without them, a reader could take an example value as a requirement.
- **Field names leak in.** It's tempting to write `uses_remaining` or `source_type`, but at logic-spec stage those names aren't decided. The examples worked better in plain words.
- **Effort is uneven.** Calculation scenarios almost write themselves from the Given / Then. Integration scenarios (external payloads, sync timing) took real effort and some could only be partly worked.
- **They can drift.** When a scenario is rewritten during review, its example has to be rewritten too, or it contradicts the rule.

## Questions for brainstorming

- **When are they written?** Options include alongside each scenario at stage 1, only after scenarios are approved, or on request.
- **Who writes them, and how are they checked?** Is a wrong example a scenario dispute, or something separate?
- **Every scenario, or only some?** Possible cut-offs: calculation and state-change scenarios only, or any scenario where the reviewer asks.
- **Where do they live?** Options include a field on each scenario in `logic.json`, a rendered block in `logic.md`, or a view in the review page.
- **How are `[ASK]` gaps routed?** Should a gap become a reviewer question, a blind spot, or a new scenario?
- **What happens on rewrite?** How does an example stay in sync when its scenario is rewritten or reopened?
- **How does the review page render them?** Does the colour coding carry over, and does an example count toward approving the scenario?
Loading
Loading