@agentsfleet/orly 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.githooks/pre-commit +42 -0
- package/.githooks/pre-push +67 -0
- package/AGENTS.md +348 -0
- package/LICENSE +21 -0
- package/README.md +249 -0
- package/SOUL.md +90 -0
- package/audits/agents-md.md +497 -0
- package/audits/agents-md.sh +353 -0
- package/audits/cross-tier-rates.sh +105 -0
- package/audits/data.sh +185 -0
- package/audits/deinit-pairs.sh +224 -0
- package/audits/design-tokens.sh +143 -0
- package/audits/doc-read.sh +183 -0
- package/audits/error-codes.sh +221 -0
- package/audits/fixtures/clean.diff +15 -0
- package/audits/fixtures/dirty.diff +16 -0
- package/audits/lifecycle-anchors.sh +22 -0
- package/audits/logging.sh +217 -0
- package/audits/msid-ui.sh +144 -0
- package/audits/parity-dispatch.sh +51 -0
- package/audits/rule-ledger-lib.sh +264 -0
- package/audits/rule-ledger.sh +225 -0
- package/audits/rule-paths.sh +108 -0
- package/audits/spec-template.sh +258 -0
- package/audits/sql-mod.sh +102 -0
- package/audits/ufs.sh +303 -0
- package/bin/orly +26 -0
- package/core/operating-model.md +256 -0
- package/dispatch/edit_rules.md +69 -0
- package/dispatch/lib.sh +213 -0
- package/dispatch/lifecycle.md +187 -0
- package/dispatch/name_architecture.md +93 -0
- package/dispatch/verify.md +111 -0
- package/dispatch/write_any.md +740 -0
- package/dispatch/write_any.sh +49 -0
- package/dispatch/write_auth.md +36 -0
- package/dispatch/write_changelog.md +64 -0
- package/dispatch/write_documentation.md +45 -0
- package/dispatch/write_go.md +24 -0
- package/dispatch/write_http.md +45 -0
- package/dispatch/write_mdx.md +7 -0
- package/dispatch/write_python.md +8 -0
- package/dispatch/write_rust.md +38 -0
- package/dispatch/write_shell.md +8 -0
- package/dispatch/write_spec.md +105 -0
- package/dispatch/write_sql.md +112 -0
- package/dispatch/write_sql.sh +36 -0
- package/dispatch/write_ts_adhere_bun.md +493 -0
- package/dispatch/write_ts_adhere_bun.sh +37 -0
- package/dispatch/write_zig.md +852 -0
- package/dispatch/write_zig.sh +40 -0
- package/docs/CHANGELOG_VOICE.md +18 -0
- package/docs/DISPATCH_ARCHITECTURE.md +664 -0
- package/docs/DISPATCH_REVIEW_DISPOSITION.md +65 -0
- package/docs/DOCUMENTATION_RULES.md +209 -0
- package/docs/EXECUTE_DOC_READS.md +29 -0
- package/docs/HARNESS_VERIFY_OUTPUT.md +40 -0
- package/docs/LIFECYCLE_PATTERNS.md +316 -0
- package/docs/LOGGING_STANDARD.md +397 -0
- package/docs/ORLY_ARCHITECTURE.md +129 -0
- package/docs/REST_API_DESIGN_GUIDELINES.md +783 -0
- package/docs/RULE_ENFORCEMENT.md +41 -0
- package/docs/SCHEMA_CONVENTIONS.md +93 -0
- package/docs/TEMPLATE.md +411 -0
- package/docs/VERIFY_TIERS.md +57 -0
- package/docs/greptile-learnings/README.md +51 -0
- package/docs/greptile-learnings/RULES.md +1045 -0
- package/fixtures/registry-invalid.json +1 -0
- package/fixtures/registry-valid.json +18 -0
- package/fixtures/unclosed-pack-block.md +8 -0
- package/package.json +63 -0
- package/packs/language/go/rules.md +24 -0
- package/packs/language/mdx/rules.md +7 -0
- package/packs/language/python/rules.md +8 -0
- package/packs/language/rust/rules.md +38 -0
- package/packs/language/shell/rules.md +8 -0
- package/registry.json +405 -0
- package/schemas/evidence.schema.json +43 -0
- package/schemas/profile.schema.json +12 -0
- package/schemas/registry.schema.json +12 -0
- package/skills/orly-babysit-prs/SKILL.md +326 -0
- package/skills/orly-spec-new/SKILL.md +157 -0
- package/skills/orly-write-integration-test/SKILL.md +470 -0
- package/skills/orly-write-unit-test/SKILL.md +520 -0
- package/src/cli.test.ts +70 -0
- package/src/cli.ts +287 -0
- package/src/config.test.ts +226 -0
- package/src/config.ts +288 -0
- package/src/criteria.test.ts +137 -0
- package/src/criteria.ts +166 -0
- package/src/criteria_spec.ts +127 -0
- package/src/criteria_support.ts +53 -0
- package/src/gates.test.ts +183 -0
- package/src/gates.ts +169 -0
- package/src/gates_spec.test.ts +98 -0
- package/src/gates_test_support.ts +194 -0
- package/src/git_env.ts +20 -0
- package/src/install.test.ts +514 -0
- package/src/install.ts +447 -0
- package/src/install_nested.test.ts +32 -0
- package/src/model.test.ts +31 -0
- package/src/model.ts +245 -0
- package/src/references.test.ts +47 -0
- package/src/references.ts +147 -0
- package/src/render.test.ts +60 -0
- package/src/render.ts +94 -0
- package/src/surfaces.test.ts +43 -0
- package/src/surfaces.ts +89 -0
- package/src/validation.test.ts +11 -0
- package/src/validation.ts +87 -0
- package/src/verify.test.ts +13 -0
- package/src/verify.ts +59 -0
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# Rule enforcement ledger
|
|
2
|
+
|
|
3
|
+
Generated by `audits/rule-ledger.sh`. Never hand-edit: `make audit`
|
|
4
|
+
regenerates this file and fails on any difference. To update it, run
|
|
5
|
+
`bash audits/rule-ledger.sh --write docs/RULE_ENFORCEMENT.md`.
|
|
6
|
+
|
|
7
|
+
| Rule document | enforced by | judged | acknowledged | classified | trigger |
|
|
8
|
+
|---|---|--:|--:|--:|---|
|
|
9
|
+
| `docs/LOGGING_STANDARD.md` | `LOG` → `logging.sh` | 10 | 13 | 26/26 | mechanical |
|
|
10
|
+
| `docs/REST_API_DESIGN_GUIDELINES.md` | — | 0 | 0 | 0/74 | mechanical |
|
|
11
|
+
| `docs/SCHEMA_CONVENTIONS.md` | — | 0 | 0 | 0/16 | mechanical |
|
|
12
|
+
| `docs/DOCUMENTATION_RULES.md` | — | 0 | 0 | 0/20 | latent |
|
|
13
|
+
| `docs/LIFECYCLE_PATTERNS.md` | — | 0 | 0 | 0/22 | mechanical |
|
|
14
|
+
| `docs/CHANGELOG_VOICE.md` | — | 0 | 0 | 0/3 | latent |
|
|
15
|
+
| `docs/VERIFY_TIERS.md` | — | 0 | 0 | 0/4 | uncited |
|
|
16
|
+
| `docs/greptile-learnings/RULES.md` | — | 0 | 0 | 0/113 | mechanical |
|
|
17
|
+
| **corpus** | | | | **26/278** | |
|
|
18
|
+
|
|
19
|
+
## How to read this
|
|
20
|
+
|
|
21
|
+
- **enforced by** — the rules in this document a script decides, and the script
|
|
22
|
+
that decides each. This is the column worth acting on: it is the difference
|
|
23
|
+
between a rule that holds and a rule that is merely written down. A code
|
|
24
|
+
reading `no helper row` is a `[DETERMINISTIC → …]` tag with nothing behind it.
|
|
25
|
+
- **judged / acknowledged** — clauses an agent must weigh (`[JUDGMENT → CODE]`)
|
|
26
|
+
and clauses nobody checks on purpose (`[UNENFORCED → reason]`, the reason
|
|
27
|
+
stated where a reader meets it).
|
|
28
|
+
- **classified** — how much of the document has been triaged at all. The
|
|
29
|
+
denominator counts lines carrying a normative keyword outside headings and
|
|
30
|
+
tables — a keyword heuristic, not a parse. It informs; it never gates.
|
|
31
|
+
- **trigger** — `mechanical` when a citing façade declares a file scope,
|
|
32
|
+
`latent` when every citing façade is prose the agent must choose to read,
|
|
33
|
+
`uncited` when no façade names the document at all.
|
|
34
|
+
|
|
35
|
+
A tag alone on its line covers every clause under it until the next heading; a
|
|
36
|
+
tag at the end of a sentence covers that sentence. Same grammar the
|
|
37
|
+
`dispatch/*.md` façades use, read by the same code.
|
|
38
|
+
|
|
39
|
+
Everything here is a function of the tree alone — no timestamps, no commit
|
|
40
|
+
hashes, no history — so the file reproduces from a checkout and any diff means
|
|
41
|
+
the corpus moved.
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
# Schema Conventions
|
|
2
|
+
|
|
3
|
+
Canonical reference for agentsfleet database schema patterns. All new tables **must** follow these conventions. Existing tables are brought into compliance when rebuilt.
|
|
4
|
+
|
|
5
|
+
## Migration Model
|
|
6
|
+
|
|
7
|
+
**Teardown-rebuild is in force for the M154 rebuild (owner decision, Indy, Jul 31, 2026).** The development database is being dropped and re-created from empty while production is undeployed, so slots `001`–`046` are **retired wholesale** and the schema is re-authored by dependency layer. During this rebuild the frozen-slot rule below does not apply, and `ALTER`/`DROP` statements are forbidden rather than required — a change belongs in the base statement it would have patched. This is the posture RULE SCH already specifies for `VERSION < 2.0.0`; the Jul 22 additive model was the deviation.
|
|
8
|
+
|
|
9
|
+
**Additive migrations resume once the rebuild lands.** Every subsequent schema change is a **new numbered migration file** — `ALTER TABLE … ADD COLUMN`, new tables, new indexes. **Shipped slot files are then frozen history: never edit an existing `schema/NNN_*.sql`.** Migrations are version-tracked and applied incrementally (expected-vs-applied state is inspectable via `agentsfleetd doctor --schema-gate`). Use `IF NOT EXISTS` guards so a migration is idempotent against both a fresh bootstrap (all slots in order) and an already-provisioned database (new slots only).
|
|
10
|
+
|
|
11
|
+
Destructive changes (`DROP TABLE`, `DROP COLUMN`, type rewrites) require an explicit owner decision per change — additive is the default an agent may author alone.
|
|
12
|
+
|
|
13
|
+
> Historical note: slots `001`–`031` predate the additive model (teardown-rebuild with inline DDL edits, enforced by a since-removed `check-schema-gate` lint target). M154 retires them along with everything up to `046`.
|
|
14
|
+
|
|
15
|
+
## Schema File Organization
|
|
16
|
+
|
|
17
|
+
- Each SQL file must be **≤100 lines** and **single-concern** (one table, one logical group, or one additive change).
|
|
18
|
+
- Files are numbered sequentially: `001_core_foundation.sql`, `002_core_workflow.sql`, etc. New migrations append the next number; shipped numbers are never reused or slid.
|
|
19
|
+
- Every SQL file must be registered in `schema/embed.zig` (compile-time embed) and `src/agentsfleetd/cmd/common.zig` (migration version array).
|
|
20
|
+
- No-op stub files (e.g., columns folded into earlier files) are kept for version history but excluded from the migrations array.
|
|
21
|
+
|
|
22
|
+
## SQL Qualification
|
|
23
|
+
|
|
24
|
+
- Use schema-qualified table names in SQL (`core.platform_llm_keys`, `core.workspaces`, etc.) for new queries and handlers.
|
|
25
|
+
- Do not rely on session `search_path` defaults for correctness.
|
|
26
|
+
- Legacy unqualified queries may remain temporarily, but touched paths should be migrated to schema-qualified names.
|
|
27
|
+
|
|
28
|
+
## Schema-Backed Runtime Defaults
|
|
29
|
+
|
|
30
|
+
- If a numeric schema default is also used in runtime fallback or provisioning logic, define it once in Zig as a named constant and import that constant in runtime code.
|
|
31
|
+
- Keep the database DDL value unchanged unless the product default is intentionally changing; the Zig constant mirrors the schema default for drift detection.
|
|
32
|
+
- Add an adjacent `Canonical constant:` SQL comment next to each shared numeric default so reviewers can verify the linkage quickly.
|
|
33
|
+
|
|
34
|
+
## Identity Column
|
|
35
|
+
|
|
36
|
+
**One identity column per table, named `id` (owner decision, Indy, Jul 31, 2026 — M154).** The earlier `uid` rule paired a generated `uid` primary key with a duplicate twin column (`id`, `tenant_id`, `workspace_id`, …) holding the same value under its own `UNIQUE` constraint. That shape is retired.
|
|
37
|
+
|
|
38
|
+
- **Column:** `id`
|
|
39
|
+
- **Type:** Universally Unique Identifier (UUID) `PRIMARY KEY`
|
|
40
|
+
- **Generation:** Application-side UUID version 7 (UUIDv7) via `src/agentsfleetd/types/id_format.zig`, never `gen_random_uuid()`.
|
|
41
|
+
- **No second unique key on the same value.** This is a correctness rule, not only a storage one: `ON CONFLICT` can arbitrate exactly one constraint, so a table carrying two unique keys over the same value makes two sessions inserting a brand-new row race to a duplicate-key error on the *other* index instead of taking the update arm. `schema/043_runner_lifetime_counters.sql` recorded this before the rule was generalised.
|
|
42
|
+
- **Foreign keys reference the primary key**, never a secondary unique constraint.
|
|
43
|
+
- **Constraint:** every table carries a UUIDv7 CHECK:
|
|
44
|
+
```sql
|
|
45
|
+
CONSTRAINT ck_{table}_id_uuidv7 CHECK (substring(id::text from 15 for 1) = '7')
|
|
46
|
+
```
|
|
47
|
+
- **Adding a new table:** add a `generate{TableName}Id()` function to `src/agentsfleetd/types/id_format.zig`.
|
|
48
|
+
- **API shape:** unchanged — public fields keep exposing `id`, `tenant_id`, `workspace_id` and the other documented names. The column now *is* the public name in most tables, so the aliasing the old rule required mostly disappears; where a public field differs from `id`, alias at the boundary rather than renaming client-facing payloads.
|
|
49
|
+
- **Exceptions,** each stated in the slot that creates the table: a curated catalogue keyed by a stable slug (`core.fleet_library`) and a singleton keyed by a pinned integer (`core.model_catalogue_revision`) carry no UUID.
|
|
50
|
+
|
|
51
|
+
## Timestamps
|
|
52
|
+
|
|
53
|
+
- **Type:** `BIGINT NOT NULL` — milliseconds since Unix epoch.
|
|
54
|
+
- **Generation:** `std.time.milliTimestamp()` in Zig application code.
|
|
55
|
+
- **Never** use `TIMESTAMPTZ`, `TIMESTAMP`, or `DEFAULT now()`.
|
|
56
|
+
|
|
57
|
+
## Standard Columns
|
|
58
|
+
|
|
59
|
+
Every table must have:
|
|
60
|
+
|
|
61
|
+
| Column | Type | Required | Notes |
|
|
62
|
+
|--------|------|----------|-------|
|
|
63
|
+
| `id` | `UUID PRIMARY KEY` | Yes | UUIDv7, app-generated; the table's only identity column |
|
|
64
|
+
| `created_at` | `BIGINT NOT NULL` | Yes | Set once at INSERT |
|
|
65
|
+
| `updated_at` | `BIGINT NOT NULL` | If mutable | Set at INSERT and every UPDATE |
|
|
66
|
+
|
|
67
|
+
**Naming is uniform.** `created_at` and `updated_at` are the only names for row lifecycle time — not `recorded_at`, `created_at_ms`, or `updated_at_ms`. A column carrying *domain* time distinct from row birth (for example the originating event's creation instant on a money row) is named for that domain meaning and documented in its slot.
|
|
68
|
+
|
|
69
|
+
**Mutable tables** (any table where UPDATE is a valid operation) must have `updated_at`.
|
|
70
|
+
|
|
71
|
+
**Append-only/event tables** (where UPDATE is blocked by trigger or by design) are exempt from `updated_at`.
|
|
72
|
+
|
|
73
|
+
## Audit Pattern
|
|
74
|
+
|
|
75
|
+
- **Actor tracking:** Use `actor TEXT` in event/transition tables (e.g., `policy_events`, `usage_ledger`).
|
|
76
|
+
- **No `updated_by` column.** Changes to mutable tables are tracked via separate event-sourced audit tables (e.g., `workspace_billing_audit`, `harness_change_log`), not inline `updated_by`.
|
|
77
|
+
- **Append-only enforcement:** Tables that must never be updated should have a trigger:
|
|
78
|
+
```sql
|
|
79
|
+
CREATE OR REPLACE FUNCTION core.{table}_append_only() RETURNS trigger AS $$
|
|
80
|
+
BEGIN
|
|
81
|
+
RAISE EXCEPTION '{table} is append-only — UPDATE and DELETE are not permitted';
|
|
82
|
+
END;
|
|
83
|
+
$$ LANGUAGE plpgsql;
|
|
84
|
+
|
|
85
|
+
CREATE TRIGGER trg_{table}_append_only
|
|
86
|
+
BEFORE UPDATE OR DELETE ON core.{table}
|
|
87
|
+
FOR EACH ROW EXECUTE FUNCTION core.{table}_append_only();
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
## RNG
|
|
91
|
+
|
|
92
|
+
- **Always** use `std.crypto.random` (via `allocUuidV7` in `id_format.zig`).
|
|
93
|
+
- **Never** use custom RNG implementations or `std.rand`.
|
package/docs/TEMPLATE.md
ADDED
|
@@ -0,0 +1,411 @@
|
|
|
1
|
+
# Milestone Specification Template
|
|
2
|
+
|
|
3
|
+
> **CANONICAL TEMPLATE — two agents consume this file, in different ways.**
|
|
4
|
+
> The **authoring agent** (via the `orly-spec-new` skill) copies the body below the divider into `docs/v{N}/pending/M{N}_{NNN}_….md` and fills it — authoring order lives in the skill, not in this file. The **executing agent** reads the filled spec **top-to-bottom**: the body is physically ordered by execution need (understand → prepare → build → prove → record).
|
|
5
|
+
> Enforced by `audits/spec-template.sh` (SPEC TEMPLATE GATE, façade `dispatch/write_spec.md`): required sections present, zero template residue, prohibited patterns absent. Lifecycle: `AGENTS.md → Specification Standards`.
|
|
6
|
+
> A shipped instance outranks guidance: when `docs/v{N}/done/` holds specs, read the newest first and pattern-match.
|
|
7
|
+
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
## Fill grammar
|
|
11
|
+
|
|
12
|
+
Three markup classes appear below the divider. The gate tells them apart mechanically:
|
|
13
|
+
|
|
14
|
+
| Marker | Meaning | Fate in the filled spec |
|
|
15
|
+
|---|---|---|
|
|
16
|
+
| `{…}` | Fill slot — replace with instance content | Gone. Surviving slots are residue; the gate BLOCKs the known sentinels. |
|
|
17
|
+
| `tpl:` guidance comment | How to fill the section — written for the authoring agent only | **Deleted after filling.** The gate BLOCKs any survivor. |
|
|
18
|
+
| SPEC AUTHORING RULES banner comment | Standing constraints on the instance | The one comment that survives, verbatim. |
|
|
19
|
+
|
|
20
|
+
Everything else — headings, table skeletons, the standard rubric rows — is kept and filled. The result: the executing agent reads **100% instance content, zero template noise**, and the 320-line budget buys signal, not boilerplate.
|
|
21
|
+
|
|
22
|
+
## What a spec pins — and refuses to pin
|
|
23
|
+
|
|
24
|
+
A milestone spec is a **goal rulebook** the executing agent plans and ships from *without playing 20-questions with the author*. It pins **intent + invariants + tests + pointers**; the agent has agency on everything else. It must NOT pin implementation detail that rots within a sprint: allocator/capacity choices, library versions, line-by-line code, exact Structured Query Language (SQL) Data Definition Language (DDL), import statements — the agent derives those from the repository.
|
|
25
|
+
|
|
26
|
+
> **Pseudocode litmus.** If the spec names a version (`postcard 1.1`), a variable (`var ctx = …`), or a DDL clause (`CREATE TABLE … DEFAULT 'foo'`), it's pseudocode — it will be wrong within a sprint. Replace with *"use the project's existing X"* + a pointer to where X lives.
|
|
27
|
+
|
|
28
|
+
## Anti-patterns (reject on sight)
|
|
29
|
+
|
|
30
|
+
| # | Anti-pattern | Do instead |
|
|
31
|
+
|---|---|---|
|
|
32
|
+
| 1 | Code blocks inside section bodies | Describe WHAT the slice delivers; pin precise behaviour as a Test. Prose code drifts; tests don't. |
|
|
33
|
+
| 2 | Listing every variable name | Point at an existing implementation to mirror; names match local style. |
|
|
34
|
+
| 3 | SQL DDL line-by-line | Table shape + constraints in prose; the agent conforms to existing migrations. |
|
|
35
|
+
| 4 | Pinning library versions | *"Use the existing Redis client"* + import pointer; the package manifest is the source of truth. |
|
|
36
|
+
| 5 | Step-by-step ordering ("Step 1… Step 2…") | **Sections** (value slices); the agent sequences within. |
|
|
37
|
+
| 6 | Test code in the spec | Name tests + assert behaviour in prose; the agent writes them in project style. |
|
|
38
|
+
| 7 | One rubric row per Dimension, or pasted evidence walls | 5–12 outcome rows; Graded = ✅/❌ + one decisive output line. The Test Specification is the per-Dimension ledger. |
|
|
39
|
+
| 8 | Template guidance surviving in the filled spec | Delete every `tpl:` comment; the executor reads instance content only. |
|
|
40
|
+
|
|
41
|
+
Slipped into one? The fix is usually **point at a file** (read-first pointer) or **delete the detail** (the agent figures it out).
|
|
42
|
+
|
|
43
|
+
## Hierarchy & terminology
|
|
44
|
+
|
|
45
|
+
```
|
|
46
|
+
v{N}.0.0 (Prototype) major release
|
|
47
|
+
└── Milestone (M40, M41…) one demoable end-to-end capability + evidence
|
|
48
|
+
└── Workstream (M40_001…) one working function; ≤4 (a 5th only for a
|
|
49
|
+
│ cross-cutting concern; never beyond 5 — split)
|
|
50
|
+
└── Section (§1, §2…) implementation slice (what + why); 3–9 per spec
|
|
51
|
+
└── Dimension (3.1, 3.2…) verifiable sub-unit — the unit of DONE
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
**Dimension** is the atomic, verifiable unit: **1 Dimension → 1 Test**, marked `DONE` individually in the same commit as its code. "Mark Dimensions DONE" (per `AGENTS.md`) resolves here. A Section is DONE when all its Dimensions are. Outcome-level grading lives in the **Acceptance Rubric** — one row per Section or failure class, never one per Dimension.
|
|
55
|
+
|
|
56
|
+
**Batch (B1, B2…)** — parallel execution group. Workstreams in the same batch run concurrently; batches are sequential (B2 starts after B1's gates clear).
|
|
57
|
+
|
|
58
|
+
A milestone is not complete until evidence is captured: commands, logs, screenshots, or a recorded walkthrough.
|
|
59
|
+
|
|
60
|
+
## Status markers
|
|
61
|
+
|
|
62
|
+
- `PENDING` — not started. `IN_PROGRESS` — being worked. `DONE` / ✅ — complete, verified, tested.
|
|
63
|
+
- `DEFERRED` — designed/attempted and explicitly not shipped; lives in `done/` as a record. **Discovery** MUST capture the rationale and reactivation conditions.
|
|
64
|
+
|
|
65
|
+
## Prohibited (gate-enforced — `dispatch/write_spec.md`)
|
|
66
|
+
|
|
67
|
+
- No time/effort estimates ("5 min", "1 hour", "2 days"). No effort columns or complexity ratings. No percentage-complete. No assigned owners. No implementation dates.
|
|
68
|
+
- Sizing signal is **Priority** (P0/P1/P2/P3); sequencing is **Dependencies**.
|
|
69
|
+
|
|
70
|
+
## Guardrails
|
|
71
|
+
|
|
72
|
+
- **Length — hard upper bound 320 lines for the filled spec** (after `tpl:` comment deletion). Typical specs 150–300. Past the bound before the Acceptance Rubric → scope is two specs sharing one file; split.
|
|
73
|
+
- **Sections** — 3–9. **Workstreams** — ≤4 (5th cross-cutting only).
|
|
74
|
+
- **File naming** — `docs/v{N}/{pending|active|done}/M{N}_{NNN}_P{Priority}_{CATEGORIES}_{NAME}.md` (e.g. `M40_001_P0_API_WORKER_SUBSTRATE.md`). Categories alphabetised.
|
|
75
|
+
|
|
76
|
+
---
|
|
77
|
+
|
|
78
|
+
# Spec Body — Copy Everything Below This Line
|
|
79
|
+
|
|
80
|
+
<!--
|
|
81
|
+
SPEC AUTHORING RULES (load-bearing — the one comment that survives):
|
|
82
|
+
- Body order = the executing agent's read order. Fill via the orly-spec-new
|
|
83
|
+
skill (authoring order lives there); after filling, DELETE every "tpl:"
|
|
84
|
+
guidance comment — the SPEC TEMPLATE GATE blocks tpl residue, unfilled
|
|
85
|
+
{slots}, and missing required sections (audits/spec-template.sh --staged).
|
|
86
|
+
- No time/effort/hour/day estimates anywhere. No effort columns, complexity
|
|
87
|
+
ratings, percentage-complete, implementation dates, assigned owners.
|
|
88
|
+
- Priority (P0/P1/P2/P3) is the only sizing signal; Dependencies are the only
|
|
89
|
+
sequencing signal. A section that contradicts these rules loses — delete it.
|
|
90
|
+
-->
|
|
91
|
+
|
|
92
|
+
# M{Milestone}_{Workstream}: {Title — testable, not vague}
|
|
93
|
+
|
|
94
|
+
**Prototype:** v{major}.{minor}.{patch}
|
|
95
|
+
**Milestone:** M{Number}
|
|
96
|
+
**Workstream:** {001-009}
|
|
97
|
+
**Date:** {MMM DD, YYYY}
|
|
98
|
+
**Status:** PENDING | IN_PROGRESS | DONE
|
|
99
|
+
**Priority:** P0 | P1 | P2 | P3 — {one-line reason}
|
|
100
|
+
**Categories:** {API | CLI | UI | SKILL | DOCS | OBS | INFRA — alphabetised, one or more}
|
|
101
|
+
**Batch:** B{1-4} — {parallel execution context}
|
|
102
|
+
**Branch:** {feat/mNN-name — added at CHORE(open)}
|
|
103
|
+
**Test Baseline:** set at CHORE(open) — `unit=<N> integration=<M>` from the repository's declared `verify.*` commands (`.oracle/orly.json`)
|
|
104
|
+
**Depends on:** {M{N}_{NNN} (one-line reason), …}
|
|
105
|
+
**Provenance:** human-written | LLM-drafted ({model}, {date}) | agent-generated (pre-spec, {source doc})
|
|
106
|
+
**Canonical architecture:** `docs/architecture/{relevant-doc}.md` §{N}
|
|
107
|
+
|
|
108
|
+
<!-- tpl: Provenance is load-bearing — the implementing agent calibrates trust by
|
|
109
|
+
who wrote the spec: LLM-drafted gets extra cross-checking against the codebase;
|
|
110
|
+
human-written assumes the author read the relevant code. Canonical architecture:
|
|
111
|
+
the docs/architecture/ directory is the source of truth (Architecture Consult &
|
|
112
|
+
Update Gate); greenfield → point at the doc that defines the shape. -->
|
|
113
|
+
|
|
114
|
+
---
|
|
115
|
+
|
|
116
|
+
## Overview
|
|
117
|
+
|
|
118
|
+
**Goal (testable):** {one sentence that could be a test name}
|
|
119
|
+
**Problem:** {observable symptoms, in user-facing terms}
|
|
120
|
+
**Solution summary:** {one paragraph — what changes, at what layer, what the user-visible outcome is}
|
|
121
|
+
|
|
122
|
+
<!-- tpl: Goal — bad: "Implement streaming." Good: "Server-Sent Events (SSE)
|
|
123
|
+
handler streams Redis pubsub messages as text/event-stream with stable ordering
|
|
124
|
+
and reconnection, p95 latency under 200ms." Problem — symptoms ("operators can't
|
|
125
|
+
see the agent's tool calls in real time"), never implementation language ("we
|
|
126
|
+
don't have a foo handler"). Implementation steps belong in Sections, not here. -->
|
|
127
|
+
|
|
128
|
+
## PR Intent & comprehension handshake
|
|
129
|
+
|
|
130
|
+
- **PR title (eventual):** {imperative, ≤72 chars — what the merged Pull Request (PR) is called}
|
|
131
|
+
- **Intent (one sentence):** {why this PR exists, in user-facing-outcome terms}
|
|
132
|
+
- **Handshake** — the implementing agent fills this at PLAN, before EXECUTE: restate the Intent in its own words and list `ASSUMPTIONS I'M MAKING: …`. A mismatch between the restatement and the Intent above → STOP and reconcile before any edit.
|
|
133
|
+
|
|
134
|
+
## Implementing agent — read these first
|
|
135
|
+
|
|
136
|
+
1. `{path/to/file.ext}` — {why this is the right pattern to mirror}
|
|
137
|
+
2. `{path/to/spec_or_doc.md}` — {what canonical knowledge lives there}
|
|
138
|
+
3. {external doc URL, if relevant} — {what convention to follow}
|
|
139
|
+
|
|
140
|
+
<!-- tpl: 3–5 pointers. Fewer than 3 = authoring homework not done (the executing
|
|
141
|
+
agent repeats it); more than 5 = a tutorial (trim). This is where judgment is
|
|
142
|
+
preserved without pseudocode: point at code/docs to read BEFORE touching any
|
|
143
|
+
file. Greenfield (no existing pattern)? Say so explicitly and point at the
|
|
144
|
+
docs/architecture/ doc that defines the shape. Every pointer must resolve
|
|
145
|
+
in THIS repository (`test -f` each) or be a URL — a pack this repository
|
|
146
|
+
never selected materialised nothing here. -->
|
|
147
|
+
|
|
148
|
+
## Files Changed (blast radius)
|
|
149
|
+
|
|
150
|
+
| File | Action | Why |
|
|
151
|
+
|------|--------|-----|
|
|
152
|
+
| `path/to/file.ext` | CREATE / EDIT / DELETE | {one line — what changes about this file's role} |
|
|
153
|
+
|
|
154
|
+
<!-- tpl: Every file created, modified, or deleted. Scopes the length gates,
|
|
155
|
+
orphan sweeps, and review effort; per AGENTS.md the executing agent may only
|
|
156
|
+
edit files in this table without explicit override. List FILES and ROLES —
|
|
157
|
+
never line numbers or function names (they drift). Teardown/rename/flip specs
|
|
158
|
+
open with a blast-radius grep first: git grep -rn -w '<token>' from repo root,
|
|
159
|
+
no path filter (dispatch/write_spec.md, Authoring discipline). -->
|
|
160
|
+
|
|
161
|
+
## Applicable Rules
|
|
162
|
+
|
|
163
|
+
- **`docs/greptile-learnings/RULES.md`** — {specific rule IDs this diff trips: e.g. UFS, NDC, NLR, ORP, FLL}
|
|
164
|
+
- {per-surface rule file} — {why it applies to this diff}
|
|
165
|
+
|
|
166
|
+
<!-- tpl: The rule files the executing agent re-reads BEFORE EXECUTE and
|
|
167
|
+
re-checks during VERIFY. Name the exact rule IDs the diff will trip — generic
|
|
168
|
+
"follow RULES.md" earns a greptile finding; named IDs are obeyed by
|
|
169
|
+
construction. Per-surface menu = the dispatch table in THIS repository's
|
|
170
|
+
generated AGENTS.md plus the docs/ rule files those façades cite — all
|
|
171
|
+
materialised locally by the selected packs; a rule file absent from this
|
|
172
|
+
repository is not applicable by definition. Fully greenfield → write "Standard
|
|
173
|
+
set only — docs/greptile-learnings/RULES.md; no other rule files apply." -->
|
|
174
|
+
|
|
175
|
+
## Applicable Gates
|
|
176
|
+
|
|
177
|
+
| Gate | Fires? | Satisfaction strategy |
|
|
178
|
+
|------|--------|-----------------------|
|
|
179
|
+
| {gate from this repository's AGENTS.md dispatch index} | {yes/no — why} | {how the diff stays clean} |
|
|
180
|
+
| File & Function Length (≤350/≤50/≤70) | {yes/no} | {split plan if a file approaches the cap} |
|
|
181
|
+
|
|
182
|
+
<!-- tpl: Which Action-Triggered Guards this Pull Request (PR) WILL trip, and
|
|
183
|
+
how each stays clean — pre-declared so the agent plans for them instead of
|
|
184
|
+
stalling mid-EXECUTE. The menu is THIS repository's generated AGENTS.md
|
|
185
|
+
dispatch index — one row per materialised gate the diff can trip; never cite a
|
|
186
|
+
gate from a pack this repository lacks. Rules ≠ Gates: rules are knowledge to
|
|
187
|
+
read; gates are guards that fire on edits. Touch nothing a gate watches →
|
|
188
|
+
replace the table with "N/A — docs/markdown only." -->
|
|
189
|
+
|
|
190
|
+
## Prior-Art / Reference Implementations
|
|
191
|
+
|
|
192
|
+
- **Reference:** {path / codebase} — {one line on alignment, or the justified divergence}
|
|
193
|
+
|
|
194
|
+
<!-- tpl: SOUL.md rule — before proposing architecture, find the reference
|
|
195
|
+
codebase; there almost always is one. Name it so the agent mirrors a known-good
|
|
196
|
+
pattern instead of inventing. Menu: Command-Line Interface (CLI) → the "7
|
|
197
|
+
Pillars" of CLI developer experience (vendored supabase-style oss/cli): command
|
|
198
|
+
→ handler → errors split; handler purity (no console.log / process.exit in
|
|
199
|
+
handlers); output as a service (human vs JSON vs env rendering chosen by the
|
|
200
|
+
renderer); structured JSON errors with suggestion/retry fields; 3-tier test
|
|
201
|
+
pyramid (handler unit / in-process integration / subprocess e2e); auto-JSON when
|
|
202
|
+
stdout is piped (LLM-native) — state which pillars this spec aligns with and any
|
|
203
|
+
divergence. API → docs/REST_API_DESIGN_GUIDELINES.md + the closest existing
|
|
204
|
+
handler. Schema → the nearest migration + docs/SCHEMA_CONVENTIONS.md. UI →
|
|
205
|
+
design-system primitives + theme.css tokens. Cite each doc only where this
|
|
206
|
+
repository materialises it. Greenfield → "no prior art; shape
|
|
207
|
+
defined in docs/architecture/{doc}.md." -->
|
|
208
|
+
|
|
209
|
+
## Sections (implementation slices)
|
|
210
|
+
|
|
211
|
+
### §1 — {Slice title}
|
|
212
|
+
|
|
213
|
+
{What this slice delivers in goal terms; why it must exist; what it unblocks. Non-obvious choice → **Implementation default:** `{choice}` because `{reason}`.}
|
|
214
|
+
|
|
215
|
+
- **Dimension 1.1** — {smallest verifiable behaviour} → Test `test_…`
|
|
216
|
+
- **Dimension 1.2** — {…} → Test `test_…`
|
|
217
|
+
|
|
218
|
+
### §2 — {Slice title}
|
|
219
|
+
|
|
220
|
+
{Same shape.}
|
|
221
|
+
|
|
222
|
+
<!-- tpl: Each Section: WHAT one slice delivers and WHY (not how); numbered
|
|
223
|
+
Dimensions map 1:1 to Tests and get marked DONE in the same commit as their
|
|
224
|
+
code. The agent picks each Implementation default unless it has evidence to
|
|
225
|
+
deviate. Good: "§3 — Replay idempotency. Receiver dedupes on the delivery id.
|
|
226
|
+
Implementation default: 24h dedupe window matching the upstream retry window;
|
|
227
|
+
storage is a Redis key with a Time To Live (TTL) — the agent picks the key
|
|
228
|
+
shape from existing dedupe patterns. Dimension 3.1 → test_dedupes_within_window;
|
|
229
|
+
3.2 → test_evicts_after_ttl." Bad: "§3 — Use redis.SET(\"webhook:dedupe:\"+id,
|
|
230
|
+
\"1\",\"NX\",\"EX\",86400) and check the return." — pseudocode; the agent reads
|
|
231
|
+
the existing dedupe pattern and writes the call. -->
|
|
232
|
+
|
|
233
|
+
## Interfaces
|
|
234
|
+
|
|
235
|
+
```
|
|
236
|
+
{HTTP endpoints, request/response shapes, internal signatures other code depends on}
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
<!-- tpl: Lock the interface — the surface the agent must NOT change without
|
|
240
|
+
amending the spec. Specify input/output/error shapes; use a real example
|
|
241
|
+
payload where shape isn't self-evident. Write the interface, not the
|
|
242
|
+
implementation. -->
|
|
243
|
+
|
|
244
|
+
## Failure Modes
|
|
245
|
+
|
|
246
|
+
| Mode | Cause | Handling (system response + what the caller observes) |
|
|
247
|
+
|------|-------|--------------------------------------------------------|
|
|
248
|
+
| {short name} | {trigger} | {response + observable} |
|
|
249
|
+
|
|
250
|
+
<!-- tpl: Every failure path the agent must handle; each row → a negative/
|
|
251
|
+
integration test in the Test Specification. Cover at minimum: timeout,
|
|
252
|
+
malformed input, auth failure, network blip, race, replay, exceeded quota,
|
|
253
|
+
dependency unavailable. -->
|
|
254
|
+
|
|
255
|
+
## Invariants
|
|
256
|
+
|
|
257
|
+
1. {Invariant} — {how it's enforced}
|
|
258
|
+
|
|
259
|
+
<!-- tpl: Each MUST be enforceable by code (compiler, lint, comptime assertion,
|
|
260
|
+
runtime check) — NOT by review discipline. If a human can violate it silently,
|
|
261
|
+
it's not an invariant. None → "N/A — no invariants." -->
|
|
262
|
+
|
|
263
|
+
## Metrics & Observability
|
|
264
|
+
|
|
265
|
+
| Metric / event | Owner | Fires when | Properties allowed | Privacy guard | Test proof |
|
|
266
|
+
|----------------|-------|------------|--------------------|---------------|------------|
|
|
267
|
+
| `{event_name}` | {product / ops / not applicable} | {exact user or system action} | {coarse product context, resource id, duration, outcome} | {no raw email/password/token/One-Time Password (OTP)/Secure Shell (SSH) key material} | `{test_name}` |
|
|
268
|
+
|
|
269
|
+
<!-- tpl: Every realized spec declares what product or operational signal it
|
|
270
|
+
adds, or explicitly why none: internal-only cleanup → the single row "not
|
|
271
|
+
applicable — no product/operator signal changes". Prefer the project's
|
|
272
|
+
analytics architecture doc (root page telemetry for views, typed event
|
|
273
|
+
registries for actions, journey timers for funnels, authenticated identity
|
|
274
|
+
linking). If a workflow already emits analytics, state whether this spec adds,
|
|
275
|
+
renames, or leaves those events unchanged; if any funnel changes, update the
|
|
276
|
+
analytics/funnel playbook in the same PR — otherwise Discovery records
|
|
277
|
+
"Metrics review: no analytics/funnel playbook update required" with the reason.
|
|
278
|
+
The implementing agent revisits this table after implementation and during
|
|
279
|
+
/review; missing action events, funnel timers, or feature-flag exposure events
|
|
280
|
+
are implementation gaps, not notes for later. -->
|
|
281
|
+
|
|
282
|
+
## Test Specification (tiered)
|
|
283
|
+
|
|
284
|
+
| Dimension | Tier | Test | Asserts (concrete inputs → expected output) |
|
|
285
|
+
|-----------|------|------|---------------------------------------------|
|
|
286
|
+
| 1.1 | {unit / integration / e2e} | `test_<short_name>` | {one-line behavioural claim} |
|
|
287
|
+
|
|
288
|
+
<!-- tpl: Prose-and-assertions only — no test code. One row per Dimension;
|
|
289
|
+
bound to the /orly-write-unit-test skill; ≥50% negative paths; every Failure Mode
|
|
290
|
+
row and every Metrics row gets a test. Tiers: unit (pure logic, handlers,
|
|
291
|
+
boundaries — empty/null/max/malformed/unicode) · integration (real stack, mock
|
|
292
|
+
only at system boundaries, deterministic failure injection per Failure Mode) ·
|
|
293
|
+
end-to-end (e2e) — any user-facing Category (CLI / UI / API) gets at least one
|
|
294
|
+
user-centric scenario via test-e2e* walking the real path (subprocess CLI /
|
|
295
|
+
real HTTP request / rendered UI); a unit test is not a substitute. Also
|
|
296
|
+
include regression rows (pre-existing behaviour that must not change — "N/A —
|
|
297
|
+
greenfield" if none) and idempotency/replay rows (any retry semantics).
|
|
298
|
+
Non-self-evident input shape → point at a fixture
|
|
299
|
+
(samples/fixtures/m{N}-fixtures/{name}.json); don't inline JSON. Hard-to-
|
|
300
|
+
describe behaviour in prose ⇒ the Goal is fuzzy — fix the Goal, not this
|
|
301
|
+
table. -->
|
|
302
|
+
|
|
303
|
+
## Acceptance Rubric (single scoring surface)
|
|
304
|
+
|
|
305
|
+
| # | Criterion (observable outcome) | Verify (copy-paste) | Expected | Priority | Graded (VERIFY) |
|
|
306
|
+
|---|--------------------------------|---------------------|----------|----------|-----------------|
|
|
307
|
+
| R1 | {outcome the user can observe} (§1) | `{command}` | {exit 0 / substring / 0 matches} | P0 | |
|
|
308
|
+
| R2 | Diff stays inside Files Changed | `git diff --name-only origin/main...HEAD` | 0 paths missing from the Files Changed table | P0 | |
|
|
309
|
+
| S1 | Conform gates green | `{conform command from .oracle/orly.json, verbatim}` | exit 0 | P0 | |
|
|
310
|
+
| S2 | Unit tests pass | `{verify.unit command from .oracle/orly.json, verbatim}` | exit 0 | P0 | |
|
|
311
|
+
| S3 | Slow tier green (code-carrying branch) | `{one row per declared slow verify.* the diff's surface needs — integration / e2e / memory / cross-compile — verbatim}` | exit 0 | P0 | |
|
|
312
|
+
| S4 | No secrets | `gitleaks detect` | exit 0 | P0 | |
|
|
313
|
+
| S5 | No oversize source file | `git diff --name-only origin/main...HEAD \| grep -v '\.md$' \| xargs wc -l 2>/dev/null \| awk '$1>350 && $2!="total"'` | no output | P0 | |
|
|
314
|
+
| S6 | Orphan sweep | Dead Code Sweep greps | 0 matches | P0 | |
|
|
315
|
+
|
|
316
|
+
**Command source rule:** every S-row Verify command is copied **verbatim from `.oracle/orly.json`** (`conform`, `verify.*`) — the same set `orly gate` runs, so the rubric and the mechanical PR gate grade one boundary. The gate BLOCKs a staged pending/active spec whose rubric omits the declared `conform` or `verify.unit` command; a rubric naming a runner the repository does not declare is wrong by construction. `.oracle/orly.json` still a seed → complete it first (`dispatch/lifecycle.md` §Bootstrap); authoring against an unseeded config is the nondeterminism this rule exists to kill.
|
|
317
|
+
|
|
318
|
+
**Grading protocol (VERIFY):** run the Verify command verbatim; grade ONLY from its output. Graded = ✅/❌ + the one decisive output line (`342 passed`); long evidence goes to PR Session Notes with a pointer here. **Ship gate:** every row graded, every P0 ✅ → eligible for CHORE(close); any ❌ or empty cell → return to EXECUTE; a P1 ❌ ships only with an Indy-acked deferral quote in Discovery.
|
|
319
|
+
|
|
320
|
+
<!-- tpl: The single scoring surface — no other scoreboard. 5–12 rows after
|
|
321
|
+
pruning: one per Section outcome, failure class, or hygiene gate — never one
|
|
322
|
+
per Dimension (that ledger is the Test Specification). S-row commands come verbatim from
|
|
323
|
+
`.oracle/orly.json`; expand S3 to one row per declared slow `verify.*` the
|
|
324
|
+
diff's surface needs; delete rows whose command this repository does not
|
|
325
|
+
declare. Expected litmus: every Expected is
|
|
326
|
+
mechanically checkable — an exit code, a literal substring, or a match count;
|
|
327
|
+
can't write it that way → the criterion is fuzzy — fix the criterion, not the
|
|
328
|
+
grading. Authoring fills every column except Graded; VERIFY fills Graded. -->
|
|
329
|
+
|
|
330
|
+
### Behaviour evals
|
|
331
|
+
|
|
332
|
+
- **Grounding rule:** {one sentence the output must never violate}
|
|
333
|
+
- **Golden set:** `samples/fixtures/{path}` — {N} cases across {3–5 coverage axes, incl. the nightmare case}. A failure found in the wild becomes a new case; the set only grows.
|
|
334
|
+
- **Ship threshold:** grounding 100% · task pass ≥{N}% · 0 critical failures on {nightmare case}. Each threshold is one rubric row with the command that computes it.
|
|
335
|
+
- **Fallback:** below threshold or low confidence → {named recoverable behaviour}; fabricated output is a P0 ❌.
|
|
336
|
+
|
|
337
|
+
<!-- tpl: Delete this whole sub-section unless the diff changes prompt/model/
|
|
338
|
+
agent behaviour. Grounding-rule example: "responses cite only retrieved rows,
|
|
339
|
+
never invented identifiers." -->
|
|
340
|
+
|
|
341
|
+
## Dead Code Sweep
|
|
342
|
+
|
|
343
|
+
**1. Orphaned files — deleted from disk and git.**
|
|
344
|
+
|
|
345
|
+
| File to delete | Verify |
|
|
346
|
+
|----------------|--------|
|
|
347
|
+
| `{path/to/old_file.ext}` | `test ! -f {path/to/old_file.ext}` |
|
|
348
|
+
|
|
349
|
+
**2. Orphaned references — zero remaining imports/uses.**
|
|
350
|
+
|
|
351
|
+
| Deleted symbol/import | Grep | Expected |
|
|
352
|
+
|-----------------------|------|----------|
|
|
353
|
+
| `{old_symbol}` | `grep -rn "{old_symbol}" src/ \| head` | 0 matches |
|
|
354
|
+
|
|
355
|
+
<!-- tpl: Mandatory when the spec deletes or replaces files: for every deleted
|
|
356
|
+
file and removed/renamed public symbol, grep the repo; non-zero = stale. Use
|
|
357
|
+
the same word-boundary pattern as the discovery grep. No deletions → replace
|
|
358
|
+
both tables with "N/A — no files deleted." -->
|
|
359
|
+
|
|
360
|
+
## Out of Scope
|
|
361
|
+
|
|
362
|
+
- {Item explicitly not in this spec — points at a follow-up spec or "future work"}
|
|
363
|
+
|
|
364
|
+
---
|
|
365
|
+
|
|
366
|
+
<!-- tpl: The two sections below are the AUTHORING RECORD — written before the
|
|
367
|
+
implementation sections above, read once by the executing agent at PLAN for
|
|
368
|
+
intent, and never re-litigated during EXECUTE. They sit last so the executor's
|
|
369
|
+
working sections stay front-loaded. -->
|
|
370
|
+
|
|
371
|
+
## Product Clarity (authoring record)
|
|
372
|
+
|
|
373
|
+
1. **Successful user moment** — {the single observable moment that proves this worked — a scene, not a metric}
|
|
374
|
+
2. **Preserved user behaviour** — {what users do today that keeps working unchanged; breaking any of it is a redesign}
|
|
375
|
+
3. **Optimal-way check** — {is this the most direct way to deliver moment #1? name the gap to the unconstrained-optimal shape and why it's acceptable now}
|
|
376
|
+
4. **Rebuild-vs-iterate** — {would a larger refactor serve better? verdict here; rationale in Decomposition below. A refactor that trades run-to-run determinism away is wrong by default}
|
|
377
|
+
5. **What we build** — {the shortest artifact list that delivers moment #1}
|
|
378
|
+
6. **What we do NOT build** — {adjacent scope rejected, one-line reason each — seeds Out of Scope}
|
|
379
|
+
7. **Fit with existing features** — {what this compounds with; the one feature it must not destabilize}
|
|
380
|
+
8. **Surface order** — {CLI-first (repo default), UI-first, or both; justify divergence}
|
|
381
|
+
9. **Dashboard restraint** — {what the UI must hide until the signal behind it is real: no controls before evidence, no quality claims before counters}
|
|
382
|
+
10. **Confused-user next step** — {the self-serve move (a command, an error message, a doc); "file a ticket" means a surface is missing from item 5}
|
|
383
|
+
|
|
384
|
+
<!-- tpl: Indy's product questions, answered in order at authoring BEFORE the
|
|
385
|
+
implementation sections are written — so the authoring agent holds the product
|
|
386
|
+
behaviour, not just the file list. One short paragraph or list item each. A
|
|
387
|
+
question that can't be answered yet is a [?] that blocks the spec (golden-path
|
|
388
|
+
rule, AGENTS.md). Internal-only work: answer 1–7 compactly; 8–10 may be "N/A —
|
|
389
|
+
no user surface" with the reason. -->
|
|
390
|
+
|
|
391
|
+
## Decomposition & alternatives (patch vs refactor)
|
|
392
|
+
|
|
393
|
+
- **Chosen shape:** {why this Section/Workstream split — the decomposition rationale}
|
|
394
|
+
- **Alternatives considered:** {≥1 — the larger refactor or the smaller patch — and why rejected for now}
|
|
395
|
+
- **Patch-vs-refactor verdict:** this is a **{patch | refactor}** because {reason}. {If a larger refactor is the right long game but out of scope, name the follow-up spec rather than silently mud-patching.}
|
|
396
|
+
|
|
397
|
+
<!-- tpl: Indy's rule — don't ship a mud-patch when the problem wants a
|
|
398
|
+
refactor, and don't refactor when a patch is right. Match solution-size to
|
|
399
|
+
problem-size; surface the call BEFORE approval, not mid-PR. Weigh against the
|
|
400
|
+
platform constants (docs/architecture/direction.md). -->
|
|
401
|
+
|
|
402
|
+
## Discovery (consult log)
|
|
403
|
+
|
|
404
|
+
- **Consults** — Architecture / Legacy-Design / gate-flag triage: the question asked + Indy's decision.
|
|
405
|
+
- **Metrics review** — events added, extra events found during `/review`, analytics/funnel playbook update or the explicit no-change reason.
|
|
406
|
+
- **Skill-chain outcomes** — `/orly-write-unit-test`, `/review`, `orly-babysit-prs` results (order per `AGENTS.md` CHORE(close); iteration counts, findings dispositioned).
|
|
407
|
+
- **Deferrals** — every "deferred to follow-up" needs an **Indy-acked verbatim quote** here, format `> Indy (YYYY-MM-DD HH:MM): "<quote>" — context: <which item, why>`. An agent-unilateral deferral is **incomplete scope, not deferral**, and blocks CHORE(close) until the item lands or the quote is captured.
|
|
408
|
+
|
|
409
|
+
<!-- tpl: Empty at creation (keep the four bullet headers). This is the spec's
|
|
410
|
+
running record — where deferrals and skill outcomes are proven as the work
|
|
411
|
+
proceeds. -->
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
# VERIFY — Tiers, performance, hygiene
|
|
2
|
+
|
|
3
|
+
> Parent: [`../AGENTS.md`](../AGENTS.md) §VERIFY. Verification Gate (`dispatch/verify.md`) enforces; `make` targets are canonical.
|
|
4
|
+
|
|
5
|
+
**FIRST: `/orly-write-unit-test`** — audits diff coverage vs spec's Test Specification (or changed surface when no spec). Iterate until clean. Skipping = CHORE(close) violation.
|
|
6
|
+
|
|
7
|
+
## Correctness tiers (do not skip)
|
|
8
|
+
|
|
9
|
+
| Tier | Command | When |
|
|
10
|
+
|---|---|---|
|
|
11
|
+
| 1 | `make test-unit-all` | Every EXECUTE iteration; start of VERIFY. Unit-only — never substitutes for 2/3. Zig-only fast loop: `make test-unit-agentsfleetd`. |
|
|
12
|
+
| 2 | `make test-integration` | Diff touches `src/agentsfleetd/http/**`, `src/agentsfleetd/db/**`, `src/agentsfleetd/fleet/**`, `src/agentsfleetd/observability/**`, `*_integration_test.zig`, schema, migrations. Before COMMIT. |
|
|
13
|
+
| 3 | `make test-integration` | ≥1× per branch from clean state (after `make down`) before ship-ready. Mandatory when schema changes pre-v2.0. Tier 2 passing + 3 failing = state pollution; fix isolation. |
|
|
14
|
+
|
|
15
|
+
## Test delta — VERIFY ends by reporting coverage growth
|
|
16
|
+
|
|
17
|
+
CHORE(open) recorded the branch-point counts in the spec header (`**Test Baseline:** unit=<N> integration=<M>`, from `make _lint_zig_test_depth`, which also writes `.tmp/agentsfleetd-test-depth.txt`). VERIFY ends with the same command and this required row in the verification block:
|
|
18
|
+
|
|
19
|
+
```
|
|
20
|
+
Test Delta: unit <N₀>→<N₁> (+x) · integration <M₀>→<M₁> (+y) vs CHORE(open) baseline
|
|
21
|
+
Lacking: <changed surfaces whose tests did not grow, or "none">
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
- **Delta is deterministic; Lacking is judgment (🤔)** — walk the diff's changed modules and name every one whose coverage did not move (e.g. "EgressScope Linux paths — integration lane pending"). "none" must be earned, not defaulted.
|
|
25
|
+
- **Zero/negative unit delta while the diff adds non-trivial code** → justify in the row (pure refactor, test consolidation) or return to EXECUTE. Deleting tests to go green is a violation, not a justification.
|
|
26
|
+
- Spec predates this rule (no `Test Baseline:` line) → reconstruct from the branch point (run the counter at the CHORE(open) commit or on `origin/main`) and add the line to the spec in the same commit as the VERIFY report.
|
|
27
|
+
|
|
28
|
+
## Performance / leak (before PR)
|
|
29
|
+
|
|
30
|
+
| Gate | Command | When |
|
|
31
|
+
|---|---|---|
|
|
32
|
+
| Leak | `make memleak` | Server lifecycle (`src/agentsfleetd/http/**`, `src/agentsfleetd/cmd/serve.zig`), allocator wiring, cross-thread heap ownership. |
|
|
33
|
+
| Bench (local) | `make bench` | When the diff touches request-path code, allocator wiring, or startup/shutdown sequencing. |
|
|
34
|
+
| Bench (dev) | `API_BENCH_URL=https://api-dev.agentsfleet.net/healthz make bench` | After deploy to dev. |
|
|
35
|
+
|
|
36
|
+
**Zig coverage.** `make test-coverage-zig` runs the daemon, runner, and three shared-library unit binaries under kcov. Each component must produce a non-empty Cobertura report. The merged report lives under `coverage/zig/merged` and must meet `ZIG_COVERAGE_MIN_LINES`.
|
|
37
|
+
|
|
38
|
+
**Integration roots.** `make test-unit-agentsfleetd` runs `src/agentsfleetd/tests.zig`. `make test-integration` prepares isolated services once and runs `src/agentsfleetd/integration_tests.zig`, which owns the live PostgreSQL, Redis, and QStash imports. The runner's Linux kernel root remains independently selectable.
|
|
39
|
+
|
|
40
|
+
**Memleak lanes.** `make memleak` leak-gates all three test graphs concurrently, each named in its own lane prefix: **`agentsfleetd`** (daemon), **`runner`** (`build_runner.zig` unit tests — integration tests fork real children and are excluded), and **`lib`** (three `src/lib` binaries). Component failures are collected and reported before the final lifecycle proof. On Linux, Valgrind is blocking and propagates its exit code. On macOS, the Zig testing allocator is blocking. The advisory `leaks` rerun happens only when one lightweight preflight proves that the host can inspect child processes. `-Dopenssl=false` is a root-graph option only; the runner graph links no OpenSSL and rejects the flag. A final **`boot-drain`** lane migrates the test database, exports the integration environment, runs the filtered daemon lifecycle test under the blocking gate, and requires its run-proof marker.
|
|
41
|
+
|
|
42
|
+
Knobs (`make/test-bench.mk`): `API_BENCH_METHOD`, `_DURATION_SEC`, `_CONCURRENCY`, `_TIMEOUT_MS`, `_MAX_ERROR_RATE`, `_MAX_P95_MS`, `_MAX_RSS_GROWTH_MB`.
|
|
43
|
+
|
|
44
|
+
**Memleak evidence:** paste final `make memleak` line into PR Session Notes OR cite CI URL. Branches touching `src/agentsfleetd/http/**`/`src/agentsfleetd/cmd/serve.zig`/allocator wiring → last 3 lines verbatim. No "trust me".
|
|
45
|
+
|
|
46
|
+
## Hygiene (always, before PR)
|
|
47
|
+
|
|
48
|
+
`make lint-all` (hard); `make _lint_zig_pg_drain` (also folded into `make lint-all` via `lint-zig`) + cross-compile `x86_64-linux` + `aarch64-linux` (any `*.zig` touched); cross-layer orphan sweep (RULE ORP — every renamed/deleted symbol → 0 hits across schema/Zig/JS/tests/docs in non-historical files); `gitleaks detect` before any Zig-including commit; 350-line / 50-fn-line check via:
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
git diff --name-only origin/main \
|
|
52
|
+
| grep -v -E '\.md$|^vendor/|_test\.|\.test\.|\.spec\.|/tests?/' \
|
|
53
|
+
| xargs -I{} sh -c 'wc -l "{}"' \
|
|
54
|
+
| awk '$1 > 350'
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
**Other:** after refactors, list newly dead code before removing — `NEWLY UNREACHABLE: <symbol/file> — <why now dead>. Remove? Confirm.` **Greptile learning capture:** each finding → "Could this recur?" If yes, add compact rule (Rule/Why/Tags/Ref) to RULES.md same commit. Never defer.
|