@nurix/etna 0.18.0 → 0.18.2
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/README.md +6 -5
- package/data/harness-index.json +1 -5
- package/data/harnesses/code/CLAUDE.md +8 -19
- package/data/harnesses/code/agents/brainstorm.md +3 -3
- package/data/harnesses/code/agents/change-review.md +13 -10
- package/data/harnesses/code/agents/harness-manager.md +15 -15
- package/data/harnesses/code/agents/maintenance.md +10 -3
- package/data/harnesses/code/agents/performance-review.md +5 -0
- package/data/harnesses/code/harness.json +1 -1
- package/data/harnesses/code/rules/general.md +40 -36
- package/data/harnesses/code/skills/boundary/SKILL.md +15 -42
- package/data/harnesses/code/skills/boundary/references/boundary-taxonomy.md +366 -304
- package/data/harnesses/code/skills/brainstorm/README.md +9 -9
- package/data/harnesses/code/skills/brainstorm/SKILL.md +15 -15
- package/data/harnesses/code/skills/brainstorm/harvest.md +3 -3
- package/data/harnesses/code/skills/brainstorm/templates/backlog.md +2 -2
- package/data/harnesses/code/skills/brainstorm/templates/build.md +6 -6
- package/data/harnesses/code/skills/brainstorm/templates/decisions.md +3 -3
- package/data/harnesses/code/skills/brainstorm/templates/doctrine.md +2 -2
- package/data/harnesses/code/skills/brainstorm/templates/domain-model.md +2 -2
- package/data/harnesses/code/skills/brainstorm/templates/glossary.md +2 -1
- package/data/harnesses/code/skills/brainstorm/templates/non-goals.md +3 -7
- package/data/harnesses/code/skills/brainstorm/templates/questions.md +6 -5
- package/data/harnesses/code/skills/brainstorm/templates/state.md +1 -8
- package/data/harnesses/code/skills/brainstorm/templates/tech.md +6 -6
- package/data/harnesses/code/skills/brainstorm/templates/thesis.md +2 -2
- package/data/harnesses/code/skills/distiller/SKILL.md +48 -177
- package/data/harnesses/code/skills/distiller/references/docs-mode.md +116 -343
- package/data/harnesses/code/skills/distiller/references/method.md +37 -109
- package/data/harnesses/code/skills/documentation/SKILL.md +9 -6
- package/data/harnesses/code/skills/documentation/references/examples.md +30 -61
- package/data/harnesses/code/skills/documentation/references/style-guide.md +59 -62
- package/data/harnesses/code/skills/documentation/references/templates.md +26 -34
- package/data/harnesses/code/skills/workflow-mapper/SKILL.md +32 -11
- package/data/harnesses/code/skills/workflow-mapper/references/patterns.md +1 -1
- package/data/harnesses/discover/CLAUDE.md +60 -113
- package/data/harnesses/discover/agents/api.md +11 -28
- package/data/harnesses/discover/agents/codebase.md +12 -30
- package/data/harnesses/discover/agents/community.md +9 -21
- package/data/harnesses/discover/agents/deployed-client-bundle.md +16 -67
- package/data/harnesses/discover/agents/distribution-artifacts.md +21 -59
- package/data/harnesses/discover/agents/docs.md +11 -20
- package/data/harnesses/discover/agents/infra-backend-fingerprint.md +28 -68
- package/data/harnesses/discover/agents/packages.md +12 -28
- package/data/harnesses/discover/agents/session.md +18 -50
- package/data/harnesses/discover/agents/website.md +9 -19
- package/data/harnesses/discover/agents/wire-capture.md +22 -72
- package/data/harnesses/discover/harness.json +1 -1
- package/data/harnesses/discover/rules/cartography-coverage.md +94 -0
- package/data/harnesses/discover/rules/cartography-flows.md +99 -0
- package/data/harnesses/discover/rules/cartography-ia.md +88 -0
- package/data/harnesses/discover/rules/cartography.md +109 -0
- package/data/harnesses/discover/rules/discovery.md +73 -182
- package/data/harnesses/discover/rules/evaluation.md +92 -98
- package/data/harnesses/discover/rules/ingestion.md +123 -322
- package/data/harnesses/discover/rules/self-correction.md +94 -107
- package/data/harnesses/discover/rules/tradecraft.md +201 -172
- package/data/harnesses/hr/CLAUDE.md +21 -51
- package/data/harnesses/hr/harness.json +1 -1
- package/data/harnesses/hr/skills/interview-eval/SKILL.md +23 -11
- package/data/harnesses/hr/skills/interview-eval/TEMPLATE.md +49 -29
- package/data/harnesses/hr/skills/interview-eval/rubric.md +30 -30
- package/data/harnesses/kb-agi/CLAUDE.md +8 -8
- package/data/harnesses/kb-agi/agents/kb-agi-init.md +31 -22
- package/data/harnesses/kb-agi/agents/kb-catch-stale.md +1 -1
- package/data/harnesses/kb-agi/agents/kb-find-gaps.md +1 -1
- package/data/harnesses/kb-agi/agents/kb-fix-links.md +1 -1
- package/data/harnesses/kb-agi/agents/kb-refresh-summaries.md +2 -2
- package/data/harnesses/kb-agi/agents/kb-sync-metadata.md +2 -2
- package/data/harnesses/kb-agi/harness.json +1 -1
- package/package.json +1 -1
- package/data/harnesses/code-fable/.prettierignore +0 -12
- package/data/harnesses/code-fable/.prettierrc.json +0 -3
- package/data/harnesses/code-fable/.vscode/settings.json +0 -10
- package/data/harnesses/code-fable/CLAUDE.md +0 -135
- package/data/harnesses/code-fable/agents/brainstorm.md +0 -35
- package/data/harnesses/code-fable/agents/change-review.md +0 -248
- package/data/harnesses/code-fable/agents/harness-manager.md +0 -181
- package/data/harnesses/code-fable/agents/maintenance.md +0 -115
- package/data/harnesses/code-fable/agents/performance-review.md +0 -218
- package/data/harnesses/code-fable/agents/spec-mode.md +0 -56
- package/data/harnesses/code-fable/harness.json +0 -12
- package/data/harnesses/code-fable/hooks/hooks.json +0 -27
- package/data/harnesses/code-fable/hooks/lib/redact.mjs +0 -35
- package/data/harnesses/code-fable/hooks/prompt-recall.mjs +0 -155
- package/data/harnesses/code-fable/hooks/session-reflect.mjs +0 -285
- package/data/harnesses/code-fable/rules/general.md +0 -79
- package/data/harnesses/code-fable/rules/nomenclature.md +0 -30
- package/data/harnesses/code-fable/rules/typescript.md +0 -19
- package/data/harnesses/code-fable/skills/boundary/SKILL.md +0 -76
- package/data/harnesses/code-fable/skills/boundary/references/boundary-taxonomy.md +0 -734
- package/data/harnesses/code-fable/skills/brainstorm/README.md +0 -136
- package/data/harnesses/code-fable/skills/brainstorm/SKILL.md +0 -135
- package/data/harnesses/code-fable/skills/brainstorm/harvest.md +0 -26
- package/data/harnesses/code-fable/skills/brainstorm/templates/backlog.md +0 -39
- package/data/harnesses/code-fable/skills/brainstorm/templates/build.md +0 -37
- package/data/harnesses/code-fable/skills/brainstorm/templates/decisions.md +0 -29
- package/data/harnesses/code-fable/skills/brainstorm/templates/doctrine.md +0 -31
- package/data/harnesses/code-fable/skills/brainstorm/templates/domain-model.md +0 -25
- package/data/harnesses/code-fable/skills/brainstorm/templates/glossary.md +0 -19
- package/data/harnesses/code-fable/skills/brainstorm/templates/milestones.md +0 -43
- package/data/harnesses/code-fable/skills/brainstorm/templates/non-goals.md +0 -21
- package/data/harnesses/code-fable/skills/brainstorm/templates/plan-doc.md +0 -61
- package/data/harnesses/code-fable/skills/brainstorm/templates/questions.md +0 -35
- package/data/harnesses/code-fable/skills/brainstorm/templates/references.md +0 -28
- package/data/harnesses/code-fable/skills/brainstorm/templates/rejected.md +0 -29
- package/data/harnesses/code-fable/skills/brainstorm/templates/state.md +0 -8
- package/data/harnesses/code-fable/skills/brainstorm/templates/tech.md +0 -23
- package/data/harnesses/code-fable/skills/brainstorm/templates/thesis.md +0 -21
- package/data/harnesses/code-fable/skills/distiller/SKILL.md +0 -219
- package/data/harnesses/code-fable/skills/distiller/references/docs-mode.md +0 -526
- package/data/harnesses/code-fable/skills/distiller/references/method.md +0 -166
- package/data/harnesses/code-fable/skills/documentation/SKILL.md +0 -125
- package/data/harnesses/code-fable/skills/documentation/references/examples.md +0 -193
- package/data/harnesses/code-fable/skills/documentation/references/style-guide.md +0 -288
- package/data/harnesses/code-fable/skills/documentation/references/templates.md +0 -865
- package/data/harnesses/code-fable/skills/workflow-mapper/SKILL.md +0 -430
- package/data/harnesses/code-fable/skills/workflow-mapper/references/patterns.md +0 -214
|
@@ -1,18 +1,10 @@
|
|
|
1
1
|
# Ingestion Contract — Capture Shape · Provenance · Ethics · Session Playbook
|
|
2
2
|
|
|
3
|
-
The reference knowledge for **Ingestion** (`discover` harness, Mode 2). Every dimension capture obeys the
|
|
4
|
-
contract in §1–8 so (a) the **evaluation** mode can rely on a stable shape and weight each claim by
|
|
5
|
-
*how it was obtained*, and (b) a future agent can reproduce or extend any capture. §9 carries the
|
|
6
|
-
deepest, gated dimension's method — the logged-in browser-session playbook — which the `session`
|
|
7
|
-
agent follows.
|
|
3
|
+
The reference knowledge for **Ingestion** (`discover` harness, Mode 2). Every dimension capture obeys the contract in §1–8 so (a) the **evaluation** mode can rely on a stable shape and weight each claim by _how it was obtained_, and (b) a future agent can reproduce or extend any capture. §9 carries the deepest, gated dimension's method — the logged-in browser-session playbook — which the `session` agent follows.
|
|
8
4
|
|
|
9
|
-
This contract generalizes the source playbook's three-format contract (`captures/*.md` narrative +
|
|
10
|
-
`raw/*.json` verbatim + `curls.md`) to *any* dimension.
|
|
5
|
+
This contract generalizes the source playbook's three-format contract (`captures/*.md` narrative + `raw/*.json` verbatim + `curls.md`) to _any_ dimension.
|
|
11
6
|
|
|
12
|
-
> **Contract style = "lighter".** Provenance ("how it was captured") lives in the
|
|
13
|
-
> **`_summary.md` frontmatter**, not a separate `manifest.yaml`. There is no parallel YAML
|
|
14
|
-
> machine-spine — everything stays markdown-with-frontmatter. Evaluation reads the frontmatter
|
|
15
|
-
> to weight claims; self-correction reads it to detect under-delivery.
|
|
7
|
+
> **Contract style = "lighter".** Provenance ("how it was captured") lives in the **`_summary.md` frontmatter**, not a separate `manifest.yaml`. There is no parallel YAML machine-spine — everything stays markdown-with-frontmatter. Evaluation reads the frontmatter to weight claims; self-correction reads it to detect under-delivery.
|
|
16
8
|
|
|
17
9
|
---
|
|
18
10
|
|
|
@@ -29,17 +21,14 @@ research/<target>/dimensions/
|
|
|
29
21
|
```
|
|
30
22
|
|
|
31
23
|
Richer dimensions inherit the playbook contract (extra files, same `_summary.md` provenance):
|
|
24
|
+
|
|
32
25
|
- **session**: `dimensions/session/{_summary.md, captures/p*.md, raw/*.json, curls.md}`
|
|
33
26
|
- **wire-capture**: `dimensions/wire-capture/{_summary.md, captures/wire-narrative.md, raw/flows.json, curls.md}`
|
|
34
27
|
|
|
35
28
|
Two layout rules the seam and the "absence is a finding" principle depend on:
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
- **A folded or absent dimension still leaves a record.** When a dimension is folded into another or
|
|
40
|
-
is absent, write a one-paragraph `_summary.md` with `status: folded` (naming the fold target) or
|
|
41
|
-
`status: absent` (+ the cheap evidence). "An absent dimension is a finding" must be **materialized
|
|
42
|
-
as a file**, never a silent drop.
|
|
29
|
+
|
|
30
|
+
- **Shared-seam files live in `dimensions/_shared/`, one physical copy each — never under a single dimension's `raw/`** (§6). Everything else under a dimension's `raw/` is **dimension-private by default**; only the §6 allowlist (`api-path-catalog.md`, `feature-flags.md`) is shared.
|
|
31
|
+
- **A folded or absent dimension still leaves a record.** When a dimension is folded into another or is absent, write a one-paragraph `_summary.md` with `status: folded` (naming the fold target) or `status: absent` (+ the cheap evidence). "An absent dimension is a finding" must be **materialized as a file**, never a silent drop.
|
|
43
32
|
|
|
44
33
|
---
|
|
45
34
|
|
|
@@ -54,7 +43,7 @@ status: complete | partial | blocked
|
|
|
54
43
|
access_grade_used: <source:full | runtime:reachable | auth:have-login | presence:rich | ...>
|
|
55
44
|
method: <the ACTUAL extraction method — see §3 vocabulary>
|
|
56
45
|
completeness_pct: <0-100, honest self-estimate of reachable surface captured>
|
|
57
|
-
confidence: high | medium | low
|
|
46
|
+
confidence: high | medium | low # how much to TRUST what was captured (see §3)
|
|
58
47
|
captured_at: <YYYY-MM-DD>
|
|
59
48
|
sources: [<urls / paths actually consulted>]
|
|
60
49
|
gaps: [<structured — each is a thing evaluation must NOT treat as covered>]
|
|
@@ -63,19 +52,23 @@ gaps: [<structured — each is a thing evaluation must NOT treat as covered>]
|
|
|
63
52
|
# <Target> — <Dimension> capture
|
|
64
53
|
|
|
65
54
|
## Method
|
|
55
|
+
|
|
66
56
|
Exactly what was done, which tools, what was skipped and why. (The prose behind the frontmatter `method`.)
|
|
67
57
|
|
|
68
58
|
## Findings
|
|
69
|
-
|
|
70
|
-
paths, page URLs, prices). Tables liberally. The load-bearing section.
|
|
59
|
+
|
|
60
|
+
Ordered, concrete observations — real values (package names, versions, endpoint paths, file paths, page URLs, prices). Tables liberally. The load-bearing section.
|
|
71
61
|
|
|
72
62
|
## Inferences
|
|
63
|
+
|
|
73
64
|
What the findings imply. Mark inference vs. fact.
|
|
74
65
|
|
|
75
66
|
## Open questions
|
|
67
|
+
|
|
76
68
|
What couldn't be captured and why. Never fabricate to fill a gap.
|
|
77
69
|
|
|
78
70
|
## Artifacts
|
|
71
|
+
|
|
79
72
|
Index of `raw/` files, one line each.
|
|
80
73
|
```
|
|
81
74
|
|
|
@@ -85,12 +78,10 @@ Keep `status` and `confidence` honest — they drive the entire evaluation weigh
|
|
|
85
78
|
|
|
86
79
|
## 3. Method & confidence vocabulary (drives evaluation weighting)
|
|
87
80
|
|
|
88
|
-
`method` is one of: `clone-and-map`, `source-map-reassembly`, `registry-metadata`,
|
|
89
|
-
`openapi-verbatim`, `graphql-introspection`, `crawl-clip`, `docs-reconstructed`,
|
|
90
|
-
`bundle-string-mine`, `dns-ct-fingerprint`, `wire-tap-browser`, `wire-har`, `wire-mitmproxy`,
|
|
91
|
-
`binary-extract` (asar/apk/crx), `session-playbook`, `sdk-harness`, `inferred`.
|
|
81
|
+
`method` is one of: `clone-and-map`, `source-map-reassembly`, `registry-metadata`, `openapi-verbatim`, `graphql-introspection`, `crawl-clip`, `docs-reconstructed`, `bundle-string-mine`, `dns-ct-fingerprint`, `wire-tap-browser`, `wire-har`, `wire-mitmproxy`, `binary-extract` (asar/apk/crx), `session-playbook`, `sdk-harness`, `inferred`.
|
|
92
82
|
|
|
93
83
|
**Confidence is a function of method** (evaluation relies on this mapping):
|
|
84
|
+
|
|
94
85
|
- **high** — verbatim machine artifacts: `openapi-verbatim`, `graphql-introspection`, `registry-metadata`, `clone-and-map`, `binary-extract`.
|
|
95
86
|
- **medium** — `crawl-clip`, `docs-reconstructed`, `bundle-string-mine`, `dns-ct-fingerprint`, `wire-har`.
|
|
96
87
|
- **low** — `inferred`, `session-playbook` Pass-1-only, `wire-tap-browser` shapes-only, any wire-drift observation without a spec.
|
|
@@ -100,87 +91,59 @@ Keep `status` and `confidence` honest — they drive the entire evaluation weigh
|
|
|
100
91
|
## 4. `raw/*` — required shape
|
|
101
92
|
|
|
102
93
|
Structured captures (API responses, registry metadata, schemas) → JSON with a `_meta` header:
|
|
94
|
+
|
|
103
95
|
```json
|
|
104
96
|
{
|
|
105
|
-
"_meta": {
|
|
106
|
-
|
|
97
|
+
"_meta": {
|
|
98
|
+
"source": "...",
|
|
99
|
+
"captured_at": "YYYY-MM-DD",
|
|
100
|
+
"kind": "...",
|
|
101
|
+
"byte_length": 0,
|
|
102
|
+
"redactions": ["field → <REDACTED:...>"]
|
|
103
|
+
},
|
|
104
|
+
"body": {
|
|
105
|
+
/* verbatim, PII + secrets redacted */
|
|
106
|
+
},
|
|
107
107
|
"_decoded": { "field": "meaning + observed values" },
|
|
108
108
|
"_observation": "optional — what's load-bearing"
|
|
109
109
|
}
|
|
110
110
|
```
|
|
111
|
-
Prose captures (doc/marketing pages, structure maps, catalogs) → markdown with a metadata line:
|
|
112
|
-
`<!-- source: <url-or-path> · captured_at: YYYY-MM-DD · method: <method> -->`
|
|
113
111
|
|
|
114
|
-
|
|
115
|
-
|
|
112
|
+
Prose captures (doc/marketing pages, structure maps, catalogs) → markdown with a metadata line: `<!-- source: <url-or-path> · captured_at: YYYY-MM-DD · method: <method> -->`
|
|
113
|
+
|
|
114
|
+
**One file per logical artifact**, named by what it is (`endpoint-catalog.md`, `react-exports.md`, `pricing.md`, `structure-map.md`).
|
|
116
115
|
|
|
117
116
|
---
|
|
118
117
|
|
|
119
118
|
## 5. Size discipline — reduce, don't dump
|
|
120
119
|
|
|
121
|
-
Large artifacts (a 3 MB bundle, a 5 MB response, a 700-page sitemap) are **digested**, not pasted:
|
|
122
|
-
the schema/shape, enum distributions, one redacted representative sample, counts. A `raw/` file is
|
|
123
|
-
readable, not a memory dump. Set `digested: true` context in the artifact's `_meta`.
|
|
120
|
+
Large artifacts (a 3 MB bundle, a 5 MB response, a 700-page sitemap) are **digested**, not pasted: the schema/shape, enum distributions, one redacted representative sample, counts. A `raw/` file is readable, not a memory dump. Set `digested: true` context in the artifact's `_meta`.
|
|
124
121
|
|
|
125
122
|
---
|
|
126
123
|
|
|
127
124
|
## 6. The shared-surface seam rule (bundle ↔ session ↔ wire-capture)
|
|
128
125
|
|
|
129
|
-
`deployed-client-bundle` (static unauth SPA), `session` (authenticated browser runtime), and
|
|
130
|
-
|
|
131
|
-
flags
|
|
132
|
-
`api-
|
|
133
|
-
|
|
134
|
-
- **
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
dimension **appends a `source:`-tagged section** (`source: bundle | session | wire | distribution`)
|
|
138
|
-
and **never creates its own copy**; each collector's `_summary.md` Artifacts list **links** the shared
|
|
139
|
-
file rather than duplicating it. **Shared-seam allowlist:** these two filenames are the *only*
|
|
140
|
-
shared/single-physical artifacts — every other `raw/*` is dimension-private.
|
|
141
|
-
- Evaluation owns the **API-path diff** (an *adaptive N-way* diff over whichever runtime sources were
|
|
142
|
-
actually run — see evaluation template 3) in `data-model-api-surface.md`. A path that appears in
|
|
143
|
-
`bundle` but never in `session`/`wire` is a dormant/unshipped route — a finding, not noise.
|
|
144
|
-
|
|
145
|
-
> **Same artifact ⇒ ONE source (never independent corroboration).** Two dimensions that string-mine
|
|
146
|
-
> the *same* static asset (one bundle / one spec / one binary) are **one source** for the promotion
|
|
147
|
-
> rule — never a confidence band-bump — and their divergent counts are a **reported range, not a
|
|
148
|
-
> flagged conflict.** Name **one owner dimension per asset**: when `source:none` and no machine spec
|
|
149
|
-
> is served, `deployed-client-bundle` **owns** the app-API bundle mine, and `api` is re-scoped to the
|
|
150
|
-
> *published/partner* spec only (or marked `partial` if none is served) — the others **cite the owner
|
|
151
|
-
> by path and do not re-mine**.
|
|
152
|
-
|
|
153
|
-
> **Published API ≠ the app's own API.** A product's developer/REST/MCP surface (`api`/`docs`/`packages`
|
|
154
|
-
> dims) is frequently *not* the API its own app runs on (`session`/`bundle` dims) — different protocol,
|
|
155
|
-
> host, and operation set. Always diff the two: operations the app uses but the public API omits, **and**
|
|
156
|
-
> public operations the app never calls, are both findings. The published surface is the integration
|
|
157
|
-
> story; the runtime surface is the real architecture. When a published API exists, Evaluation emits
|
|
158
|
-
> this split as a *named* output, not inline prose (evaluation template 3).
|
|
126
|
+
`deployed-client-bundle` (static unauth SPA), `session` (authenticated browser runtime), and `wire-capture` (non-browser client) can all surface the same **API path catalog** and **feature flags**. To keep them consumable instead of triplicated **N times** (a real run forked `api-path-catalog.md` up to 7×, and "two copies agree" then mis-reads as corroboration):
|
|
127
|
+
|
|
128
|
+
- **One physical file, one canonical location, append-only.** The seam files live at `dimensions/_shared/api-path-catalog.md` and `dimensions/_shared/feature-flags.md` — **Ingestion seeds the two empty files (with a header) once, before the collector fan-out.** Every contributing dimension **appends a `source:`-tagged section** (`source: bundle | session | wire | distribution`) and **never creates its own copy**; each collector's `_summary.md` Artifacts list **links** the shared file rather than duplicating it. **Shared-seam allowlist:** these two filenames are the _only_ shared/single-physical artifacts — every other `raw/*` is dimension-private.
|
|
129
|
+
- Evaluation owns the **API-path diff** (an _adaptive N-way_ diff over whichever runtime sources were actually run — see evaluation template 3) in `data-model-api-surface.md`. A path that appears in `bundle` but never in `session`/`wire` is a dormant/unshipped route — a finding, not noise.
|
|
130
|
+
|
|
131
|
+
> **Same artifact ⇒ ONE source (never independent corroboration).** Two dimensions that string-mine the _same_ static asset (one bundle / one spec / one binary) are **one source** for the promotion rule — never a confidence band-bump — and their divergent counts are a **reported range, not a flagged conflict.** Name **one owner dimension per asset**: when `source:none` and no machine spec is served, `deployed-client-bundle` **owns** the app-API bundle mine, and `api` is re-scoped to the _published/partner_ spec only (or marked `partial` if none is served) — the others **cite the owner by path and do not re-mine**.
|
|
132
|
+
|
|
133
|
+
> **Published API ≠ the app's own API.** A product's developer/REST/MCP surface (`api`/`docs`/`packages` dims) is frequently _not_ the API its own app runs on (`session`/`bundle` dims) — different protocol, host, and operation set. Always diff the two: operations the app uses but the public API omits, **and** public operations the app never calls, are both findings. The published surface is the integration story; the runtime surface is the real architecture. When a published API exists, Evaluation emits this split as a _named_ output, not inline prose (evaluation template 3).
|
|
159
134
|
>
|
|
160
|
-
> **Paired inputs make the diff feedable (capture cue).** The split is only emittable if BOTH lanes were
|
|
161
|
-
> captured: when a target has **a published API AND an authed session**, the **pre-nav in-page fetch/XHR
|
|
162
|
-
> tap** (`session` — the app-own paths it actually calls) and the **unauth bundle/route-manifest**
|
|
163
|
-
> (`deployed-client-bundle` — the published paths) are **paired required inputs**. Install the tap *before*
|
|
164
|
-
> navigation (else on-load app calls are missed) and capture the manifest unauth; a session that only
|
|
165
|
-
> screenshots, or a bundle mine with no session, leaves the diff un-feedable. (Origin: Akeneo — 54
|
|
166
|
-
> session-observed app-own paths vs a 124-path unauth `/api/rest/v1` manifest, near-disjoint.)
|
|
135
|
+
> **Paired inputs make the diff feedable (capture cue).** The split is only emittable if BOTH lanes were captured: when a target has **a published API AND an authed session**, the **pre-nav in-page fetch/XHR tap** (`session` — the app-own paths it actually calls) and the **unauth bundle/route-manifest** (`deployed-client-bundle` — the published paths) are **paired required inputs**. Install the tap _before_ navigation (else on-load app calls are missed) and capture the manifest unauth; a session that only screenshots, or a bundle mine with no session, leaves the diff un-feedable. (Origin: Akeneo — 54 session-observed app-own paths vs a 124-path unauth `/api/rest/v1` manifest, near-disjoint.)
|
|
167
136
|
>
|
|
168
|
-
> **Host-level dimensions cite, they don't append (seam allowlist).** The shared `api-path-catalog.md`
|
|
169
|
-
> `## source:` allowlist is **bundle / session / wire / distribution** only — the path-contributing
|
|
170
|
-
> runtime lanes. A host-level dimension (`infra-backend-fingerprint`) that wants to cross-reference the
|
|
171
|
-
> catalog does so **from its own `raw/`** (cite hosts/paths by reference), and does **not** append its own
|
|
172
|
-
> `## source:` section to the shared file. (Origin: Akeneo — infra appended a host-level note to the path
|
|
173
|
-
> catalog; benign but blurs the allowlist.)
|
|
137
|
+
> **Host-level dimensions cite, they don't append (seam allowlist).** The shared `api-path-catalog.md` `## source:` allowlist is **bundle / session / wire / distribution** only — the path-contributing runtime lanes. A host-level dimension (`infra-backend-fingerprint`) that wants to cross-reference the catalog does so **from its own `raw/`** (cite hosts/paths by reference), and does **not** append its own `## source:` section to the shared file. (Origin: Akeneo — infra appended a host-level note to the path catalog; benign but blurs the allowlist.)
|
|
174
138
|
|
|
175
139
|
---
|
|
176
140
|
|
|
177
141
|
## 7. Ethics & redaction (non-negotiable — binds EVERY dimension)
|
|
178
142
|
|
|
179
|
-
Violating them voids the work. Rules 2–3 are enforced by the **runnable redaction layer in §7.0** —
|
|
180
|
-
install it inside the tap wrapper so no secret can reach `raw/` in the first place.
|
|
143
|
+
Violating them voids the work. Rules 2–3 are enforced by the **runnable redaction layer in §7.0** — install it inside the tap wrapper so no secret can reach `raw/` in the first place.
|
|
181
144
|
|
|
182
145
|
1. **Never enter credentials.** The user signs into any logged-in surface beforehand.
|
|
183
|
-
2. **Storage: shape, not values.** No raw cookies/tokens/JWT-signatures. Decode JWT
|
|
146
|
+
2. **Storage: shape, not values.** No raw cookies/tokens/JWT-signatures. Decode JWT _structure_ with identity claims (`sub`,`email`,`phone`,`user_id`,`uid`,`sid`) redacted.
|
|
184
147
|
3. **Never save secrets.** Before any `raw/` write, redact every credential-shaped field → `<REDACTED:...>` + log in `_meta.redactions`. Field patterns: `api_key`,`*_secret`,`client_secret`,`webhook_secret`,`signing_*`,`password`,`private_key`,`access_token`/`refresh_token` in bodies, vendor-named secrets. Value patterns: `sk_live_…`/`pk_live_…` (Stripe), `sk-…` (OpenAI), `xox[bp]-…` (Slack), `AKIA…` (AWS), `gh[ps]_…` (GitHub), `AIza…` (Google), `eyJ…` (JWT), opaque base64 ≥32 chars in a credential field. Over-redact when unsure. **The executable scrubbers that enforce this rule are in §7.0 below** (`SCRUB` / `redactSecrets` / `ULTRA`); the full keyboard layer lives in `tradecraft.md`.
|
|
185
148
|
4. **Confirm before state changes** — anything that creates state, costs money, or is visible to others (even a draft toggle). State the exact action + estimated cost.
|
|
186
149
|
5. **No destructive actions.** Document the absence; move on.
|
|
@@ -188,36 +151,40 @@ install it inside the tap wrapper so no secret can reach `raw/` in the first pla
|
|
|
188
151
|
7. **No exfiltration.** Captures stay local under `research/<target>/`.
|
|
189
152
|
8. **Respect TOS & robots.** For spec-writing / integration / competitive analysis — not credential theft, rate-limit bypass, or scraping.
|
|
190
153
|
9. **One browser-driving dimension at a time.** Chrome MCP is a shared singleton. Only the gated `session`/`wire-capture` dimensions drive the browser; parallel non-browser collectors use WebFetch/curl, or — if a page genuinely needs a real browser — create their **own** tab and never touch the session tab. Never run two browser-driving collectors concurrently.
|
|
191
|
-
10. **Verify an absence before asserting it.** A "host is NXDOMAIN / dimension is absent / endpoint 404s everywhere" claim that would
|
|
192
|
-
11. **Bounded live-token use (read-only, in-page only).** To fill a Pass-1 capture gap you MAY read the live token from its store and replay a **read-only** request with it —
|
|
154
|
+
10. **Verify an absence before asserting it.** A "host is NXDOMAIN / dimension is absent / endpoint 404s everywhere" claim that would _contradict another dimension_ must be re-checked by a **second method** (alternate resolver, direct HTTP probe, second source) before it is written as fact. A single-resolver/single-probe negative is recorded as _tentative_, never as a cross-dimension directive. (An HTTP 4xx/5xx means the host **resolves** — present, not absent.) **Feedback-channel / changelog absence (`community`): a 404 on the marketing host's `/changelog` is NOT proof of "no public feedback loop"** — before asserting it, probe the common alternate homes: `feedback.`/`changelog.`/`updates.`/`roadmap.` subdomains, hosted boards (FeatureOS/Canny/Featurebase), a status-page "announcements" tab, and `<link rel="alternate" type="application/rss+xml">` autodiscovery on the docs/blog. Only after those miss is the closed-feedback cap (evaluation) warranted; a wrong-host 404 alone leaves it _tentative_.
|
|
155
|
+
11. **Bounded live-token use (read-only, in-page only).** To fill a Pass-1 capture gap you MAY read the live token from its store and replay a **read-only** request with it — _only_ if the token is used **in-page / in-process only**, is **never written to `raw/`, never returned, never logged**, and the replay changes no state. This is the single permitted touch of a live credential; rules 2–3 (shape-not-values, never-save-secrets) still bind everything written to disk.
|
|
193
156
|
|
|
194
157
|
### 7.0 Redaction — runnable (the executable layer behind rule 3)
|
|
195
158
|
|
|
196
|
-
These are **executable guardrails**, not prose policy. Install them **inside the tap/fetch wrapper** so every captured string is scrubbed
|
|
159
|
+
These are **executable guardrails**, not prose policy. Install them **inside the tap/fetch wrapper** so every captured string is scrubbed _before it reaches the harness or disk_ — credentials never reach `raw/`, and the browser MCP's content-block (`[BLOCKED: Cookie/query string data]`) never fires on a legitimate capture. Run `SCRUB(value)` on every string returned from a `javascript_tool` capture; run `redactSecrets(body, redactions)` on every object before any `Write` of `raw/*.json`; fall back to `ULTRA` only when the harness still blocks output.
|
|
197
160
|
|
|
198
161
|
#### 1. `SCRUB` — the value-shape scrubber (run on every captured string)
|
|
199
162
|
|
|
200
163
|
Combine into one `SCRUB` function and apply to every string returned from `javascript_tool`. It neutralises PII + provider-credential value shapes regardless of field name (catches credentials embedded in string concatenations like a deep-link URL or a log line):
|
|
201
164
|
|
|
202
165
|
```js
|
|
203
|
-
const SCRUB = (s) =>
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
166
|
+
const SCRUB = (s) =>
|
|
167
|
+
String(s || "")
|
|
168
|
+
.replace(/eyJ[A-Za-z0-9_\-]+\.[A-Za-z0-9_\-]+\.[A-Za-z0-9_\-]+/g, "<jwt>")
|
|
169
|
+
.replace(/[A-Za-z0-9+/]{40,}={0,2}/g, "<b64>")
|
|
170
|
+
.replace(/data:image\/[^"]+/g, "<data-image>")
|
|
171
|
+
.replace(/\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}\b/g, "<email>")
|
|
172
|
+
.replace(
|
|
173
|
+
/[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}/g,
|
|
174
|
+
"<uuid>",
|
|
175
|
+
)
|
|
176
|
+
.replace(/[a-f0-9]{32,}/g, "<hex>")
|
|
177
|
+
.replace(/(user|org|ins|sess|cli)_[A-Za-z0-9]{20,}/g, ".$1id.") // Clerk-style IDs
|
|
178
|
+
.replace(/\+91\d{10}/g, "<phone-in>") // Indian phone numbers
|
|
179
|
+
.replace(/\+1\d{10}/g, "<phone-us>") // US phone numbers
|
|
180
|
+
.replace(/\b\d{10,15}\b/g, "<phone>") // generic numeric phone
|
|
181
|
+
.replace(/[?&]([a-z_]+)=([^&\s"]+)/gi, (_, k, v) => "?" + k + "=<v>");
|
|
215
182
|
```
|
|
216
183
|
|
|
217
184
|
The regex table behind `SCRUB` (PII value shapes + known provider-key prefixes), so a new run can extend it:
|
|
218
185
|
|
|
219
186
|
| Pattern | Replacement | Catches |
|
|
220
|
-
|
|
187
|
+
| --- | --- | --- |
|
|
221
188
|
| `eyJ[A-Za-z0-9_\-]+\.[A-Za-z0-9_\-]+\.[A-Za-z0-9_\-]+` | `<jwt>` | JWT tokens (3-segment base64) |
|
|
222
189
|
| `[A-Za-z0-9+/]{40,}={0,2}` | `<b64>` | Long base64 |
|
|
223
190
|
| `data:image/[^"]+` | `<data-image>` | Inline image data URIs |
|
|
@@ -239,19 +206,20 @@ The regex table behind `SCRUB` (PII value shapes + known provider-key prefixes),
|
|
|
239
206
|
|
|
240
207
|
#### 2. `redactSecrets` — the field-name walker (run before every `raw/*.json` write)
|
|
241
208
|
|
|
242
|
-
`SCRUB` catches known value
|
|
209
|
+
`SCRUB` catches known value _shapes_; some providers use opaque base64 tokens with **no recognisable prefix**, so you also need a recursive walker that redacts by **key name** regardless of value shape. Run both — `SCRUB` on captured strings, `redactSecrets` on the parsed object before any `Write`:
|
|
243
210
|
|
|
244
211
|
```js
|
|
245
|
-
const SECRET_KEY_RE =
|
|
212
|
+
const SECRET_KEY_RE =
|
|
213
|
+
/(api[_-]?key|apikey|secret|password|passphrase|pwd|client[_-]?secret|webhook[_-]?secret|signing[_-]?(?:key|secret)|private[_-]?key|service[_-]?account[_-]?key|access[_-]?token|refresh[_-]?token|bearer[_-]?token|auth[_-]?token)$/i;
|
|
246
214
|
|
|
247
215
|
function redactSecrets(obj, redactions) {
|
|
248
|
-
if (obj === null || typeof obj !==
|
|
249
|
-
if (Array.isArray(obj)) return obj.map(v => redactSecrets(v, redactions));
|
|
216
|
+
if (obj === null || typeof obj !== "object") return obj;
|
|
217
|
+
if (Array.isArray(obj)) return obj.map((v) => redactSecrets(v, redactions));
|
|
250
218
|
const out = {};
|
|
251
219
|
for (const [k, v] of Object.entries(obj)) {
|
|
252
|
-
if (typeof v ===
|
|
220
|
+
if (typeof v === "string" && SECRET_KEY_RE.test(k) && v.length > 0) {
|
|
253
221
|
redactions.push(`${k} → <REDACTED:SECRET> (was ${v.length} chars)`);
|
|
254
|
-
out[k] =
|
|
222
|
+
out[k] = "<REDACTED:SECRET>";
|
|
255
223
|
} else {
|
|
256
224
|
out[k] = redactSecrets(v, redactions);
|
|
257
225
|
}
|
|
@@ -265,11 +233,7 @@ const safeBody = redactSecrets(rawBody, redactions);
|
|
|
265
233
|
// Then write { _meta: { ..., redactions }, body: safeBody, _decoded_fields: {...} }
|
|
266
234
|
```
|
|
267
235
|
|
|
268
|
-
At run time, append the **current session's** own identifiers (your user UUID, email, display name,
|
|
269
|
-
org slug) as explicit `.replace(...)` lines in a final pass over the serialized output — but **never
|
|
270
|
-
commit those literal values into this file.** The generic UUID / email / phone / Clerk-ID patterns
|
|
271
|
-
above already neutralise them; the explicit pass is a belt-and-suspenders step that stays local to the
|
|
272
|
-
run.
|
|
236
|
+
At run time, append the **current session's** own identifiers (your user UUID, email, display name, org slug) as explicit `.replace(...)` lines in a final pass over the serialized output — but **never commit those literal values into this file.** The generic UUID / email / phone / Clerk-ID patterns above already neutralise them; the explicit pass is a belt-and-suspenders step that stays local to the run.
|
|
273
237
|
|
|
274
238
|
For a logged-in session, also redact: the user's display name, the user's avatar URL, any organisation slug (`org_slug` claim) the user might want hidden, and phone numbers at any masking level. Over-redaction is cheap; a leaked partner API key is not.
|
|
275
239
|
|
|
@@ -278,7 +242,7 @@ For a logged-in session, also redact: the user's display name, the user's avatar
|
|
|
278
242
|
When `SCRUB` isn't enough (e.g. the body has agent-generated content with patterns that match the blocklist), drop to a regex that strips everything except readable ASCII:
|
|
279
243
|
|
|
280
244
|
```js
|
|
281
|
-
const ULTRA = (s) => String(s||
|
|
245
|
+
const ULTRA = (s) => String(s || "").replace(/[^a-zA-Z0-9 _.,:!?\-/]/g, ".");
|
|
282
246
|
```
|
|
283
247
|
|
|
284
248
|
You lose JSON braces and special characters, but you keep the **structural information** (field names, enum values, lengths) — useful for getting any output at all when the body is irretrievably full of blocked patterns.
|
|
@@ -301,94 +265,44 @@ When a run uncovers a new key or value shape (a regional BSP / telephony token,
|
|
|
301
265
|
|
|
302
266
|
### 7.1 wire-capture & active-interception ethics (the strictest tier)
|
|
303
267
|
|
|
304
|
-
The `wire-capture` dimension climbs a **3-rung ladder, lowest privilege first**, and records the
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
*user* installs the CA and routes their client — the agent only ingests exported flows. →
|
|
316
|
-
`method: wire-mitmproxy`. Same explicit-authorization loop as session Pass-2.
|
|
317
|
-
|
|
318
|
-
> **Rung 3+ — the first-party SDK-harness lane (the deepest method for a closed runtime that PUBLISHES
|
|
319
|
-
> an SDK).** When the product ships an official client SDK (npm / PyPI / Maven / …) and the *user* can
|
|
320
|
-
> provision credentials (free tier / trial), the deepest no-source method is not to intercept the
|
|
321
|
-
> *vendor's* app but to **scaffold a minimal throw-away app with the product's OWN SDK + the user's keys,
|
|
322
|
-
> run it, and instrument the client you control**: inject fetch/WS/EventSource taps at `document_start`
|
|
323
|
-
> (so there is **no interceptor-timing problem** — you wrap the constructors before any connection
|
|
324
|
-
> opens), observe every request/response shape, and — with the user routing it through *their own*
|
|
325
|
-
> mitmproxy — the fully-decoded wire. Because you **own** the client, there is no third-party-traffic
|
|
326
|
-
> concern and no missed on-load socket. **Gated exactly like rung 3 / session Pass-2:** the *user*
|
|
327
|
-
> provisions the account + keys and runs any proxy; the **agent never self-signs-up and never enters
|
|
328
|
-
> credentials**, only scaffolds + instruments, and confirms any cost/quota first. → `method: sdk-harness`
|
|
329
|
-
> (a first-party runtime lane; confidence = what is actually observed, typically high since you control
|
|
330
|
-
> the client). Especially powerful for `runtime:reachable + source:none` targets whose published SDK is
|
|
331
|
-
> the only public mirror of the runtime — and it independently corroborates the `api`/`packages` lanes.
|
|
332
|
-
|
|
333
|
-
> **Never fold a browser-rung into a non-browser (curl/WebFetch) collector.** Rung 1 *is* a browser tap,
|
|
334
|
-
> and the browser is a singleton owned by `session`/`wire-capture` — routing it to a curl collector
|
|
335
|
-
> produces zero `source: wire` rows (a real run did exactly this). When `wire-capture` is **folded**,
|
|
336
|
-
> the rung-1 tap is the **in-page interceptor that `session` already installs**: `session` appends its
|
|
337
|
-
> observed paths to `dimensions/_shared/api-path-catalog.md` as `source: wire`. A folded `wire-capture`
|
|
338
|
-
> still leaves a one-paragraph `_summary.md` (`status: folded`, naming the fold target) per §1 —
|
|
339
|
-
> "folded" is recorded, never silently dropped.
|
|
340
|
-
|
|
341
|
-
> Wireshark / packet capture is explicitly **not** used: for HTTPS it yields only TLS ciphertext,
|
|
342
|
-
> and a packet sweep of a third party is the bright line this harness will not cross.
|
|
268
|
+
The `wire-capture` dimension climbs a **3-rung ladder, lowest privilege first**, and records the chosen rung in `_meta`:
|
|
269
|
+
|
|
270
|
+
1. **Browser network tap (default, unauth).** Chrome `read_network_requests` + the in-page fetch/XHR interceptor on the **public** surface only. Capture request/response _shapes_; redact in the wrapper before disk. No login. → `method: wire-tap-browser`.
|
|
271
|
+
2. **HAR export.** Export DevTools HAR of the unauth SPA's XHRs, digest to an endpoint catalog, scrub `Authorization`/`Cookie`/secret-shaped values. → `method: wire-har`.
|
|
272
|
+
3. **mitmproxy / intercepting proxy — GATED, opt-in, user-run.** ONLY first-party traffic the **user owns** (their own desktop/mobile/CLI client), ONLY with explicit chat authorization, ONLY TOS-checked. The harness NEVER stands up a proxy against a third party's infra unattended; the _user_ installs the CA and routes their client — the agent only ingests exported flows. → `method: wire-mitmproxy`. Same explicit-authorization loop as session Pass-2.
|
|
273
|
+
|
|
274
|
+
> **Rung 3+ — the first-party SDK-harness lane (the deepest method for a closed runtime that PUBLISHES an SDK).** When the product ships an official client SDK (npm / PyPI / Maven / …) and the _user_ can provision credentials (free tier / trial), the deepest no-source method is not to intercept the _vendor's_ app but to **scaffold a minimal throw-away app with the product's OWN SDK + the user's keys, run it, and instrument the client you control**: inject fetch/WS/EventSource taps at `document_start` (so there is **no interceptor-timing problem** — you wrap the constructors before any connection opens), observe every request/response shape, and — with the user routing it through _their own_ mitmproxy — the fully-decoded wire. Because you **own** the client, there is no third-party-traffic concern and no missed on-load socket. **Gated exactly like rung 3 / session Pass-2:** the _user_ provisions the account + keys and runs any proxy; the **agent never self-signs-up and never enters credentials**, only scaffolds + instruments, and confirms any cost/quota first. → `method: sdk-harness` (a first-party runtime lane; confidence = what is actually observed, typically high since you control the client). Especially powerful for `runtime:reachable + source:none` targets whose published SDK is the only public mirror of the runtime — and it independently corroborates the `api`/`packages` lanes.
|
|
275
|
+
|
|
276
|
+
> **Never fold a browser-rung into a non-browser (curl/WebFetch) collector.** Rung 1 _is_ a browser tap, and the browser is a singleton owned by `session`/`wire-capture` — routing it to a curl collector produces zero `source: wire` rows (a real run did exactly this). When `wire-capture` is **folded**, the rung-1 tap is the **in-page interceptor that `session` already installs**: `session` appends its observed paths to `dimensions/_shared/api-path-catalog.md` as `source: wire`. A folded `wire-capture` still leaves a one-paragraph `_summary.md` (`status: folded`, naming the fold target) per §1 — "folded" is recorded, never silently dropped.
|
|
277
|
+
|
|
278
|
+
> Wireshark / packet capture is explicitly **not** used: for HTTPS it yields only TLS ciphertext, and a packet sweep of a third party is the bright line this harness will not cross.
|
|
343
279
|
|
|
344
280
|
### 7.2 distribution-artifacts ethics
|
|
345
|
-
|
|
346
|
-
But binary **decompilation** can cross from "inspect" to "decompile in violation of EULA":
|
|
347
|
-
the agent reads the artifact's own license/EULA first and records a restrictive one as an Open
|
|
348
|
-
question rather than silently decompiling.
|
|
281
|
+
|
|
282
|
+
Downloading a public binary (CRX/`.asar`/`.ipa`/`.apk`) and reading its manifest/strings is fine. But binary **decompilation** can cross from "inspect" to "decompile in violation of EULA": the agent reads the artifact's own license/EULA first and records a restrictive one as an Open question rather than silently decompiling.
|
|
349
283
|
|
|
350
284
|
---
|
|
351
285
|
|
|
352
286
|
## 8. Completeness check
|
|
353
|
-
|
|
354
|
-
every Finding is backed by a `raw/` artifact or inline value; `raw/` artifacts have `_meta` +
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
(downloading many chunks, crawling many pages, deep pagination), then enrich it in place and flip to
|
|
360
|
-
`complete`. **Bound** every fan-out — cap it by count / total bytes / wall-time (e.g. ≤40 items or
|
|
361
|
-
≤90 s) and record the remainder as a `gap` rather than fetching exhaustively; emit a progress note per
|
|
362
|
-
batch. A collector that fetches everything before writing anything can run many times longer than its
|
|
363
|
-
siblings and read as hung to the orchestrator, forcing a needless salvage. Partial-but-finalized beats
|
|
364
|
-
complete-but-silent.
|
|
365
|
-
|
|
366
|
-
**Capability gap = a checkpoint, not a silent fallback.** When a *preferred* capture capability is found
|
|
367
|
-
**unavailable at runtime** — a CDP/remote-debugging port is closed, Chrome / the browser MCP is unreachable,
|
|
368
|
-
a gated proxy or tool can't be stood up — such that you would proceed on a **materially weaker** method
|
|
369
|
-
(e.g. source-only or a digest instead of a live wire tap), **pause and confirm with the user before
|
|
370
|
-
continuing.** State plainly: what is unavailable, the degraded method you would fall back to, and the
|
|
371
|
-
coverage that would be lost. This is distinct from a *planned* method pre-grade (e.g. `openapi-verbatim` →
|
|
372
|
-
`docs-reconstructed` when no spec is served), which is **recorded, not confirmed** — the checkpoint is for an
|
|
373
|
-
*infrastructure/capability* gap discovered mid-run, where silently degrading hides a real coverage loss
|
|
374
|
-
behind a confident-looking capture. (A real run found the CDP port closed and silently fell back to a
|
|
375
|
-
browser-MCP tap; it worked, but the degradation should have been surfaced first.)
|
|
287
|
+
|
|
288
|
+
A capture is complete when: `_summary.md` exists with full provenance frontmatter + honest `status`; every Finding is backed by a `raw/` artifact or inline value; `raw/` artifacts have `_meta` + decoding (large ones digested); redaction pass run; `gaps` recorded (never fabricated).
|
|
289
|
+
|
|
290
|
+
**Summary-first + bounded, visible fan-out (never go silent long enough to look dead).** Write a `status: partial` `_summary.md` with whatever is mined so far **before** any long-tail fetch loop (downloading many chunks, crawling many pages, deep pagination), then enrich it in place and flip to `complete`. **Bound** every fan-out — cap it by count / total bytes / wall-time (e.g. ≤40 items or ≤90 s) and record the remainder as a `gap` rather than fetching exhaustively; emit a progress note per batch. A collector that fetches everything before writing anything can run many times longer than its siblings and read as hung to the orchestrator, forcing a needless salvage. Partial-but-finalized beats complete-but-silent.
|
|
291
|
+
|
|
292
|
+
**Capability gap = a checkpoint, not a silent fallback.** When a _preferred_ capture capability is found **unavailable at runtime** — a CDP/remote-debugging port is closed, Chrome / the browser MCP is unreachable, a gated proxy or tool can't be stood up — such that you would proceed on a **materially weaker** method (e.g. source-only or a digest instead of a live wire tap), **pause and confirm with the user before continuing.** State plainly: what is unavailable, the degraded method you would fall back to, and the coverage that would be lost. This is distinct from a _planned_ method pre-grade (e.g. `openapi-verbatim` → `docs-reconstructed` when no spec is served), which is **recorded, not confirmed** — the checkpoint is for an _infrastructure/capability_ gap discovered mid-run, where silently degrading hides a real coverage loss behind a confident-looking capture. (A real run found the CDP port closed and silently fell back to a browser-MCP tap; it worked, but the degradation should have been surfaced first.)
|
|
376
293
|
|
|
377
294
|
---
|
|
378
295
|
|
|
379
296
|
## 9. The session dimension — logged-in browser-session playbook
|
|
380
297
|
|
|
381
|
-
The method for the **session** dimension: discovering the
|
|
382
|
-
product by driving it through a browser (Chrome MCP) and instrumenting it. It produces a
|
|
383
|
-
section-by-section understanding of a product's wire protocol, auth, and state model **without
|
|
384
|
-
source-code access**. The `session` agent follows this section.
|
|
298
|
+
The method for the **session** dimension: discovering the _authenticated runtime_ of a product by driving it through a browser (Chrome MCP) and instrumenting it. It produces a section-by-section understanding of a product's wire protocol, auth, and state model **without source-code access**. The `session` agent follows this section.
|
|
385
299
|
|
|
386
300
|
This is the deepest and most sensitive dimension. The ethics in §7 above bind hardest here.
|
|
387
301
|
|
|
388
302
|
### 9.1 Prerequisites (verify before anything else — all three non-negotiable)
|
|
389
303
|
|
|
390
304
|
| # | Precondition | How to verify |
|
|
391
|
-
|
|
305
|
+
| --- | --- | --- |
|
|
392
306
|
| 1 | Target is **pre-logged-in** in Chrome | open the target URL in the connected tab → an authenticated surface (a dashboard, not a sign-in page). The agent never enters credentials. |
|
|
393
307
|
| 2 | **Claude Chrome extension** installed | `mcp__claude-in-chrome__tabs_context_mcp` returns live tabs |
|
|
394
308
|
| 3 | **Chrome CDP** connected | the same call returns stable tab IDs |
|
|
@@ -397,50 +311,22 @@ If any fails, return `blocked` naming the missing precondition.
|
|
|
397
311
|
|
|
398
312
|
### 9.2 The three-pass strategy
|
|
399
313
|
|
|
400
|
-
A single linear sweep wastes context on things you can read for free and wastes credits on
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
`entitlements`/`permissions` arrays** (the single richest data-model artifact — `entitlements`/`features`
|
|
410
|
-
enumerate every gated feature *including ones your plan hides*; `permissions` are CRUD-verbs-per-entity,
|
|
411
|
-
so the verb set enumerates the domain entity set — `X_CREATE/VIEW/EDIT/DELETE` ⇒ entity `X`; mine these
|
|
412
|
-
first), integration
|
|
413
|
-
catalog (`/mcp/`, `/integrations/`), and schema/capabilities (`/openapi.json`, `/.well-known/*`,
|
|
414
|
-
`/page-metadata`).
|
|
415
|
-
|
|
416
|
-
**Pass 2 — fresh-run validation (with explicit cost/state-change confirmation).** Only after
|
|
417
|
-
Pass 1, and only with explicit user OK stating the exact action + cheapest mode + estimated cost.
|
|
418
|
-
Catches what Pass 1 can't: the submit/write request body, live state-machine transitions, the
|
|
419
|
-
completion event, server-side resolution of shorthand, ID/slug-generation, the real iteration
|
|
420
|
-
endpoint, and the full realtime event catalog. **Legitimate to skip** if the user opts out — leave
|
|
421
|
-
write-side shapes as open questions; never fabricate them.
|
|
422
|
-
|
|
423
|
-
> **Record the write-side decision as a machine flag, not re-derived prose.** A Pass-1-only run leaves
|
|
424
|
-
> the *entire* write/generate/launch surface unobserved — feature *existence/gating* is solid, but
|
|
425
|
-
> feature *execution behavior* rests on marketing/docs and must never be promoted to fact (this caveat
|
|
426
|
-
> otherwise gets re-hedged across 3–4 rollups every cost-gated run). So the session `_summary.md`
|
|
427
|
-
> frontmatter carries `write_side_observed: <true|false>` and `pass2: <not-applicable | offered-pending
|
|
428
|
-
> | declined-by-user | deferred | done>` (this flag is the load-bearing part — it lets Evaluation render
|
|
429
|
-
> the write-side gap *once*). When it would help a later run resume, optionally add
|
|
430
|
-
> `captures/pass2-followup.md` listing the **exact cheapest-mode write actions + est. cost** that would
|
|
431
|
-
> close the gap. Discovery seeds the `pass2:` decision in `00-recon-plan.md` for any `auth:have-login`
|
|
432
|
-
> target.
|
|
433
|
-
|
|
434
|
-
**Pass 3 — synthesis.** Walk the captures, pull each finding into the dimension's `_summary.md`,
|
|
435
|
-
note the cleverest design choices, defaults imposed, performance characteristics, and open
|
|
436
|
-
questions.
|
|
314
|
+
A single linear sweep wastes context on things you can read for free and wastes credits on actions whose response shape you could infer. Use three passes.
|
|
315
|
+
|
|
316
|
+
**Pass 1 — read-only mining (no money, no state change).** If the user already has data (a project, conversation, account, order), mine it first. Hit every list/state/history endpoint with the existing token; capture verbatim bodies. This yields ~70% of the spec for free. Mine, by role: identity (`/me`, `/account`, `/workspaces`), list (the canonical taxonomy), single-resource state (server-injected vs client-sent fields), history/event-log (a whole trajectory in one call), runtime/preview state, per-resource billing, account billing, feature flags/cohort, **the account's `entitlements`/`permissions` arrays** (the single richest data-model artifact — `entitlements`/`features` enumerate every gated feature _including ones your plan hides_; `permissions` are CRUD-verbs-per-entity, so the verb set enumerates the domain entity set — `X_CREATE/VIEW/EDIT/DELETE` ⇒ entity `X`; mine these first), integration catalog (`/mcp/`, `/integrations/`), and schema/capabilities (`/openapi.json`, `/.well-known/*`, `/page-metadata`).
|
|
317
|
+
|
|
318
|
+
**Pass 2 — fresh-run validation (with explicit cost/state-change confirmation).** Only after Pass 1, and only with explicit user OK stating the exact action + cheapest mode + estimated cost. Catches what Pass 1 can't: the submit/write request body, live state-machine transitions, the completion event, server-side resolution of shorthand, ID/slug-generation, the real iteration endpoint, and the full realtime event catalog. **Legitimate to skip** if the user opts out — leave write-side shapes as open questions; never fabricate them.
|
|
319
|
+
|
|
320
|
+
> **Record the write-side decision as a machine flag, not re-derived prose.** A Pass-1-only run leaves the _entire_ write/generate/launch surface unobserved — feature _existence/gating_ is solid, but feature _execution behavior_ rests on marketing/docs and must never be promoted to fact (this caveat otherwise gets re-hedged across 3–4 rollups every cost-gated run). So the session `_summary.md` frontmatter carries `write_side_observed: <true|false>` and `pass2: <not-applicable | offered-pending | declined-by-user | deferred | done>` (this flag is the load-bearing part — it lets Evaluation render the write-side gap _once_). When it would help a later run resume, optionally add `captures/pass2-followup.md` listing the **exact cheapest-mode write actions + est. cost** that would close the gap. Discovery seeds the `pass2:` decision in `00-recon-plan.md` for any `auth:have-login` target.
|
|
321
|
+
|
|
322
|
+
**Pass 3 — synthesis.** Walk the captures, pull each finding into the dimension's `_summary.md`, note the cleverest design choices, defaults imposed, performance characteristics, and open questions.
|
|
437
323
|
|
|
438
324
|
### 9.3 The P0 → P6 phases
|
|
439
325
|
|
|
440
326
|
The same phases apply whether the target is an AI builder, analytics dashboard, CRM, or collab tool:
|
|
441
327
|
|
|
442
328
|
| Phase | Focus |
|
|
443
|
-
|
|
329
|
+
| --- | --- |
|
|
444
330
|
| **P0 Surface** | routes, bundles, storage shape, the API host(s), the auth tell; build the **coverage checklist** from any route/op catalog a sibling declared (§9.4) |
|
|
445
331
|
| **P1 Auth** | token type + issuer + storage location + refresh path; tenancy model; **disambiguate the transport by omission** (replay one authed read with each credential mechanism dropped) — and never test auth with a schema-only field |
|
|
446
332
|
| **P2 Core action** | the primary write — the submit/create body and its server resolution |
|
|
@@ -451,116 +337,31 @@ The same phases apply whether the target is an AI builder, analytics dashboard,
|
|
|
451
337
|
|
|
452
338
|
### 9.4 Runtime taps (Pass-1 instrumentation, run in-page)
|
|
453
339
|
|
|
454
|
-
> **The runnable taps + the ~27-entry Chrome-MCP gotcha catalogue + the dead-end recovery table + the
|
|
455
|
-
|
|
456
|
-
>
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
> on Next.js). The principle is framework-agnostic; the specific call is just whatever that app ships.
|
|
468
|
-
|
|
469
|
-
- **WebSocket / EventSource / fetch+XHR interceptors** — wrap the constructors/methods to log
|
|
470
|
-
URLs, frames, and bodies (shapes, not secrets — redact in the wrapper before anything reaches
|
|
471
|
-
disk). Add a `SCRUB(value)` step inside the fetch wrapper so credentials never reach the harness.
|
|
472
|
-
**Never log a binary frame as opaque `<binary>`.** Any realtime/streaming/collab layer (token
|
|
473
|
-
streams, CRDT/OT edits, presence, telemetry, game or market state) may ship `ArrayBuffer`/`Blob`
|
|
474
|
-
frames; for each *distinct* frame capture its **byte-length + a hex dump of the first ~16 bytes** —
|
|
475
|
-
enough to fingerprint the wire format (protobuf, MessagePack, CBOR, Yjs/CRDT, gRPC-web, or a
|
|
476
|
-
text/topic-prefixed channel protocol) without recording content. **Decode the signature as ASCII
|
|
477
|
-
first** — many "binary" protocols are framed text (STOMP, Phoenix, Centrifugo, a `+topic` channel
|
|
478
|
-
prefix), so a legible prefix names the protocol in one step before a binary-codec guess. The frame
|
|
479
|
-
*signature* is the finding; the payload is not.
|
|
480
|
-
- **Realtime-on-load capture (the connection that opens before you can wrap it).** The "install the
|
|
481
|
-
interceptor FIRST" rule assumes you are *already on* the target surface; it does **not** cover a
|
|
482
|
-
realtime connection that fires during the **initial load of a page you `navigate` to** (or a
|
|
483
|
-
lazy-loaded/`iframe`d live demo). A post-load interceptor **cannot** wrap a `WebSocket`/`EventSource`
|
|
484
|
-
constructed before injection, and an `offline`→`online`/`focus` reconnect-trigger may spawn no new
|
|
485
|
-
connection on such a demo — so frame capture comes back empty (a real run got 0 frames on a
|
|
486
|
-
collab-demo this way). When that happens: (a) capture the **CDP-visible auth/token-mint fetch** that
|
|
487
|
-
precedes the socket (it names the realtime host/handshake); (b) **the technique that works —
|
|
488
|
-
client-side-navigate within the SPA** (a router `<Link>` click, not a full reload): the in-page
|
|
489
|
-
wrapper **survives client-side nav**, and the next route **mounts a fresh room → a new socket through
|
|
490
|
-
your wrapper** (this captures the handshake + frames where `offline`/`online` reconnect-triggers
|
|
491
|
-
don't — a real run decoded Liveblocks' v8 JSON protocol this way after a 0-frame first pass); (c) if a
|
|
492
|
-
raw **CDP `Network.webSocketFrameReceived`** tap is reachable, it captures frames protocol-level with
|
|
493
|
-
no injection-timing problem at all (the browser MCP's HTTP-only `read_network_requests` does **not**
|
|
494
|
-
surface WS frames — that gap is what forces (b)); and (d) fall back to the `codebase`/`distribution`
|
|
495
|
-
lanes for the wire format — record it as a *folded* gap, not a silent miss, since the protocol is then
|
|
496
|
-
sourced independently rather than live-observed. **Decode captured frames against any in-repo protocol
|
|
497
|
-
enums** (message-code / op-code tables) so the live wire *validates* the source read.
|
|
498
|
-
- **Storage shape inspector** — enumerate localStorage / sessionStorage / cookies / IndexedDB
|
|
499
|
-
**key names + lengths**, never raw values.
|
|
500
|
-
- **JWT decoder** — decode the token *structure* (alg, claim names, exp) with identity claims
|
|
501
|
-
(`sub`, `email`, `phone`, `user_id`, `uid`, `sid`) redacted.
|
|
502
|
-
- **Auth-channel disambiguation.** Token *location* is itself a tell: not in localStorage ⇒ an httpOnly
|
|
503
|
-
cookie **or** an SDK store (IndexedDB — Firebase/Auth0/Supabase/Cognito keep tokens there, which is why
|
|
504
|
-
a "no JWT in localStorage" result is a finding, not a dead end). Determine the *required* transport by
|
|
505
|
-
**omission**: replay one harmless authed read — using a field that needs the authed context (`viewer`/
|
|
506
|
-
`me`), **never** a schema-only field (GraphQL `__typename`, a health route) that resolves
|
|
507
|
-
unauthenticated — with each candidate credential dropped (cookie-only, then header-only); whichever
|
|
508
|
-
response comes back unauthorized names the channel the app actually requires.
|
|
509
|
-
- If a realtime SDK is loaded (Pusher, Ably, Socket.IO, Centrifugo, Phoenix, …), bind a global listener
|
|
510
|
-
to capture the event/channel catalog.
|
|
511
|
-
- **Third-party-SDK fingerprint** (cross-checks `infra-backend-fingerprint`). Storage key names + the
|
|
512
|
-
network host list name the embedded vendor stack — auth (Firebase/Auth0), flags (LaunchDarkly/Statsig),
|
|
513
|
-
support (Intercom/Zendesk/Ada), analytics (Segment/Amplitude/Datadog), payments (Stripe). Record them;
|
|
514
|
-
they corroborate the sub-processor list *independently* (a different dimension → promotes confidence).
|
|
515
|
-
- **Drive op coverage from any catalog a sibling declared.** If `deployed-client-bundle` (or `api`/`docs`)
|
|
516
|
-
recovered a declared **route / endpoint / operation catalog** — a REST path table, a GraphQL operation
|
|
517
|
-
set, an RPC/tool method list — consume it as a **coverage checklist**: walk the routes/actions that
|
|
518
|
-
trigger the un-observed entries so the declared-vs-observed diff (§6) reflects a real walk, not just the
|
|
519
|
-
landing page.
|
|
520
|
-
- **Server-side-read (RSC) check — don't mis-record "no read API."** In a modern Next.js App-Router /
|
|
521
|
-
React-Server-Components app (and similar SSR/streaming stacks), the initial **read** data is fetched
|
|
522
|
-
*server-side* with the session and streamed — so the client wire shows only mutations, asset/media
|
|
523
|
-
fetches, telemetry, and token-mints, and **no client-side data-read XHRs fire on load even though the
|
|
524
|
-
UI is fully populated.** Before recording "read API not observed," check for the server-side path: a
|
|
525
|
-
document/`?_rsc=` response with `text/x-component`, embedded `__next_f`/flight payloads, `_next/data`
|
|
526
|
-
JSON, or server actions. When reads are server-resolved, say so — and note that a sibling
|
|
527
|
-
bundle's app-own *path count* then **overstates the client read surface** (fold this into the
|
|
528
|
-
published-vs-app-own diff, §6, rather than reporting a phantom "unused routes" gap). (Writes can be
|
|
529
|
-
off-main-thread too — a generate POST issued from a **Web Worker** won't appear in a main-thread fetch
|
|
530
|
-
tap; corroborate via CDP / Performance resource timing.)
|
|
340
|
+
> **The runnable taps + the ~27-entry Chrome-MCP gotcha catalogue + the dead-end recovery table + the Apollo read-side recipe + the fingerprint tables live in `tradecraft.md`** — paste them, do not re-derive. The prose below is the _why_; that file is the _keyboard layer_. The §7.0 redaction scrubbers (`SCRUB`/`redactSecrets`/`ULTRA`) install inside these tap wrappers.
|
|
341
|
+
|
|
342
|
+
> **Install the in-page interceptor FIRST, then move section-to-section WITHOUT a full reload.** CDP `read_network_requests` is unreliable for an already-loaded single-page app: it starts tracking only when first called and silently misses requests already in flight or served from cache (seen on a real run where only a third-party analytics beacon was captured while the app's own API calls went unseen). Treat it as a _supplement_; the in-page interceptor is the primary capture. A full-page `navigate` wipes the interceptor, so traverse the app using whatever it exposes — **clicking in-app nav works on any stack**; or call the client router if one is reachable (`history.pushState`+`popstate`, React Router / Vue Router / Angular Router, or a globally-exposed router such as `window.next.router` on Next.js). The principle is framework-agnostic; the specific call is just whatever that app ships.
|
|
343
|
+
|
|
344
|
+
- **WebSocket / EventSource / fetch+XHR interceptors** — wrap the constructors/methods to log URLs, frames, and bodies (shapes, not secrets — redact in the wrapper before anything reaches disk). Add a `SCRUB(value)` step inside the fetch wrapper so credentials never reach the harness. **Never log a binary frame as opaque `<binary>`.** Any realtime/streaming/collab layer (token streams, CRDT/OT edits, presence, telemetry, game or market state) may ship `ArrayBuffer`/`Blob` frames; for each _distinct_ frame capture its **byte-length + a hex dump of the first ~16 bytes** — enough to fingerprint the wire format (protobuf, MessagePack, CBOR, Yjs/CRDT, gRPC-web, or a text/topic-prefixed channel protocol) without recording content. **Decode the signature as ASCII first** — many "binary" protocols are framed text (STOMP, Phoenix, Centrifugo, a `+topic` channel prefix), so a legible prefix names the protocol in one step before a binary-codec guess. The frame _signature_ is the finding; the payload is not.
|
|
345
|
+
- **Realtime-on-load capture (the connection that opens before you can wrap it).** The "install the interceptor FIRST" rule assumes you are _already on_ the target surface; it does **not** cover a realtime connection that fires during the **initial load of a page you `navigate` to** (or a lazy-loaded/`iframe`d live demo). A post-load interceptor **cannot** wrap a `WebSocket`/`EventSource` constructed before injection, and an `offline`→`online`/`focus` reconnect-trigger may spawn no new connection on such a demo — so frame capture comes back empty (a real run got 0 frames on a collab-demo this way). When that happens: (a) capture the **CDP-visible auth/token-mint fetch** that precedes the socket (it names the realtime host/handshake); (b) **the technique that works — client-side-navigate within the SPA** (a router `<Link>` click, not a full reload): the in-page wrapper **survives client-side nav**, and the next route **mounts a fresh room → a new socket through your wrapper** (this captures the handshake + frames where `offline`/`online` reconnect-triggers don't — a real run decoded Liveblocks' v8 JSON protocol this way after a 0-frame first pass); (c) if a raw **CDP `Network.webSocketFrameReceived`** tap is reachable, it captures frames protocol-level with no injection-timing problem at all (the browser MCP's HTTP-only `read_network_requests` does **not** surface WS frames — that gap is what forces (b)); and (d) fall back to the `codebase`/`distribution` lanes for the wire format — record it as a _folded_ gap, not a silent miss, since the protocol is then sourced independently rather than live-observed. **Decode captured frames against any in-repo protocol enums** (message-code / op-code tables) so the live wire _validates_ the source read.
|
|
346
|
+
- **Storage shape inspector** — enumerate localStorage / sessionStorage / cookies / IndexedDB **key names + lengths**, never raw values.
|
|
347
|
+
- **JWT decoder** — decode the token _structure_ (alg, claim names, exp) with identity claims (`sub`, `email`, `phone`, `user_id`, `uid`, `sid`) redacted.
|
|
348
|
+
- **Auth-channel disambiguation.** Token _location_ is itself a tell: not in localStorage ⇒ an httpOnly cookie **or** an SDK store (IndexedDB — Firebase/Auth0/Supabase/Cognito keep tokens there, which is why a "no JWT in localStorage" result is a finding, not a dead end). Determine the _required_ transport by **omission**: replay one harmless authed read — using a field that needs the authed context (`viewer`/ `me`), **never** a schema-only field (GraphQL `__typename`, a health route) that resolves unauthenticated — with each candidate credential dropped (cookie-only, then header-only); whichever response comes back unauthorized names the channel the app actually requires.
|
|
349
|
+
- If a realtime SDK is loaded (Pusher, Ably, Socket.IO, Centrifugo, Phoenix, …), bind a global listener to capture the event/channel catalog.
|
|
350
|
+
- **Third-party-SDK fingerprint** (cross-checks `infra-backend-fingerprint`). Storage key names + the network host list name the embedded vendor stack — auth (Firebase/Auth0), flags (LaunchDarkly/Statsig), support (Intercom/Zendesk/Ada), analytics (Segment/Amplitude/Datadog), payments (Stripe). Record them; they corroborate the sub-processor list _independently_ (a different dimension → promotes confidence).
|
|
351
|
+
- **Drive op coverage from any catalog a sibling declared.** If `deployed-client-bundle` (or `api`/`docs`) recovered a declared **route / endpoint / operation catalog** — a REST path table, a GraphQL operation set, an RPC/tool method list — consume it as a **coverage checklist**: walk the routes/actions that trigger the un-observed entries so the declared-vs-observed diff (§6) reflects a real walk, not just the landing page.
|
|
352
|
+
- **Server-side-read (RSC) check — don't mis-record "no read API."** In a modern Next.js App-Router / React-Server-Components app (and similar SSR/streaming stacks), the initial **read** data is fetched _server-side_ with the session and streamed — so the client wire shows only mutations, asset/media fetches, telemetry, and token-mints, and **no client-side data-read XHRs fire on load even though the UI is fully populated.** Before recording "read API not observed," check for the server-side path: a document/`?_rsc=` response with `text/x-component`, embedded `__next_f`/flight payloads, `_next/data` JSON, or server actions. When reads are server-resolved, say so — and note that a sibling bundle's app-own _path count_ then **overstates the client read surface** (fold this into the published-vs-app-own diff, §6, rather than reporting a phantom "unused routes" gap). (Writes can be off-main-thread too — a generate POST issued from a **Web Worker** won't appear in a main-thread fetch tap; corroborate via CDP / Performance resource timing.)
|
|
531
353
|
|
|
532
354
|
### 9.5 The capture contract (richer than a standard dimension capture)
|
|
533
355
|
|
|
534
356
|
Under `research/<target>/dimensions/session/`:
|
|
535
357
|
|
|
536
|
-
- **`captures/p*.md`** — per-phase narrative for the human: frontmatter (title, summary, scope,
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
- **`
|
|
541
|
-
|
|
542
|
-
redacted) + `_decoded_fields`. Digest bodies > 50 KB into a structured summary.
|
|
543
|
-
- **`curls.md`** — reproducible curl per endpoint, the auth model documented, a variables block,
|
|
544
|
-
and an endpoint-catalog table.
|
|
545
|
-
- **Visual / surface map** — `captures/screens/` (**preferred when available**): one annotated
|
|
546
|
-
screenshot per primary surface (each major nav section), redact visible PII before saving. **When
|
|
547
|
-
screenshot capture is unavailable** (e.g. the runtime lacks an image tool, or the app holds a
|
|
548
|
-
persistent connection that never reaches page-idle), a **textual `captures/surface-map.md` is the
|
|
549
|
-
accepted substitute** and satisfies this visual-map requirement — and the **visual-coverage gap is
|
|
550
|
-
recorded in the `gaps:` frontmatter** so Evaluation reads a *known constraint*, not a silent miss.
|
|
551
|
-
- **`captures/pass2-followup.md`** (optional, when Pass 2 is deferred/declined and it would help a
|
|
552
|
-
later run resume) — the exact cheapest-mode write/generate actions + estimated cost that would close
|
|
553
|
-
the write-side gap (§9.2). The `write_side_observed`/`pass2` frontmatter flags are mandatory; this
|
|
554
|
-
file is the convenience.
|
|
555
|
-
- **`_summary.md`** — the standard dimension bridge file (provenance frontmatter per §2) pointing
|
|
556
|
-
into the above, so Evaluation treats session like any other dimension. `method: session-playbook`,
|
|
557
|
-
`confidence: low` for Pass-1-only/inferred. **Session frontmatter additionally carries
|
|
558
|
-
`write_side_observed: <true|false>` and `pass2: <not-applicable | offered-pending | declined-by-user
|
|
559
|
-
| deferred | done>`** (§9.2) so Evaluation renders the write-side gap once, machine-readably.
|
|
358
|
+
- **`captures/p*.md`** — per-phase narrative for the human: frontmatter (title, summary, scope, status), **Method** (taps run, actions taken), **Findings** (concrete values — URLs, fields, status codes), **Inferences**, **Diff vs reference**, **Open questions**. Decoded, opinionated; don't paste raw bodies here.
|
|
359
|
+
- **`raw/*.json`** — verbatim endpoint captures for the reproducer: a `_meta` header (endpoint, auth-shape, captured_at, status, byte_length, redactions) + `body` (PII + secrets redacted) + `_decoded_fields`. Digest bodies > 50 KB into a structured summary.
|
|
360
|
+
- **`curls.md`** — reproducible curl per endpoint, the auth model documented, a variables block, and an endpoint-catalog table.
|
|
361
|
+
- **Visual / surface map** — `captures/screens/` (**preferred when available**): one annotated screenshot per primary surface (each major nav section), redact visible PII before saving. **When screenshot capture is unavailable** (e.g. the runtime lacks an image tool, or the app holds a persistent connection that never reaches page-idle), a **textual `captures/surface-map.md` is the accepted substitute** and satisfies this visual-map requirement — and the **visual-coverage gap is recorded in the `gaps:` frontmatter** so Evaluation reads a _known constraint_, not a silent miss. (The per-surface screenshot set is then **completed in Mode 4 — Cartography** (`cartography.md` §D), which re-enters the live product to capture each surface alongside its IA depth; the session-pass surface-map is the seed, the Cartography screens are the finished deliverable, and `screenshot_coverage` is the measured gap.)
|
|
362
|
+
- **`captures/pass2-followup.md`** (optional, when Pass 2 is deferred/declined and it would help a later run resume) — the exact cheapest-mode write/generate actions + estimated cost that would close the write-side gap (§9.2). The `write_side_observed`/`pass2` frontmatter flags are mandatory; this file is the convenience.
|
|
363
|
+
- **`_summary.md`** — the standard dimension bridge file (provenance frontmatter per §2) pointing into the above, so Evaluation treats session like any other dimension. `method: session-playbook`, `confidence: low` for Pass-1-only/inferred. **Session frontmatter additionally carries `write_side_observed: <true|false>` and `pass2: <not-applicable | offered-pending | declined-by-user | deferred | done>`** (§9.2) so Evaluation renders the write-side gap once, machine-readably.
|
|
560
364
|
|
|
561
365
|
### 9.6 Session ethics (binds hardest here — see §7 above)
|
|
562
366
|
|
|
563
|
-
Never enter credentials; storage shape not values; redact every secret **inside the tap wrapper**
|
|
564
|
-
before it reaches disk; **confirm before any Pass-2 state change or cost** (exact action + exact
|
|
565
|
-
cost); no destructive actions; no bypassing the harness (redact, never evade); captures stay local;
|
|
566
|
-
respect TOS. The provenance/redaction rules in §1–8 apply to everything written here.
|
|
367
|
+
Never enter credentials; storage shape not values; redact every secret **inside the tap wrapper** before it reaches disk; **confirm before any Pass-2 state change or cost** (exact action + exact cost); no destructive actions; no bypassing the harness (redact, never evade); captures stay local; respect TOS. The provenance/redaction rules in §1–8 apply to everything written here.
|