@postman/postman-plugin 0.5.0-rc.0 → 0.5.1

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.
@@ -2,17 +2,43 @@ import { GIT_URL, REPO, isSameRepo, redact } from '../source.js';
2
2
  import { blocked, guard, mustProbeText, mustRun } from './shared.js';
3
3
  import { result } from './types.js';
4
4
  const MARKETPLACE = 'postman-plugin', PLUGIN_ID = `postman@${MARKETPLACE}`, SCOPE = 'user', NEXT = 'Restart Droid for the change to take effect.';
5
+ function rowsOfWrappedTable(text) {
6
+ const lines = text.split(/\r?\n/), headerAt = lines.findIndex((line) => /^[A-Z]+(?: [A-Z]+)*(?: {2,}[A-Z]+(?: [A-Z]+)*)+\s*$/.test(line));
7
+ if (headerAt === -1) {
8
+ return [];
9
+ }
10
+ const columns = [...lines[headerAt].matchAll(/[A-Z]+(?: [A-Z]+)*/g)].map((match) => ({ name: match[0], start: match.index })), rows = [];
11
+ for (const line of lines.slice(headerAt + 1)) {
12
+ const cells = columns.map(({ start }, i) => line.slice(start, columns[i + 1]?.start).trim()), previous = rows.at(-1), continuesPrevious = !cells[0];
13
+ if (!continuesPrevious) {
14
+ rows.push(Object.fromEntries(columns.map(({ name }, i) => [name, cells[i]])));
15
+ }
16
+ else if (previous) {
17
+ columns.forEach(({ name }, i) => { previous[name] += cells[i]; });
18
+ }
19
+ }
20
+ return rows;
21
+ }
22
+ export function installedPlugins(droidPluginList) {
23
+ const bracketedScopes = droidPluginList.split(/\r?\n/)
24
+ .map((line) => /^\s*(\S+@\S+)\s+\[(\w+)\]/.exec(line))
25
+ .filter((match) => match !== null)
26
+ .map(([, plugin, scope]) => `${plugin} [${scope}]`);
27
+ return bracketedScopes.length ?
28
+ bracketedScopes :
29
+ rowsOfWrappedTable(droidPluginList).map((row) => `${row.PLUGIN} [${row.SOURCE}]`);
30
+ }
5
31
  function hasInstalledPlugin(text) {
6
- return text.split('\n').some((line) => new RegExp(`^${PLUGIN_ID}\\s+\\[${SCOPE}\\]`).test(line.trimStart()));
32
+ return installedPlugins(text).includes(`${PLUGIN_ID} [${SCOPE}]`);
7
33
  }
8
34
  function marketplaceSource(text) {
9
- for (const line of text.split('\n')) {
35
+ for (const line of text.split(/\r?\n/)) {
10
36
  const match = /^\s*(\S+)\s+\(\d+ plugins?\)\s+(.+?)(?:\s+"[^"]*")?\s*$/.exec(line);
11
37
  if (match && match[1] === MARKETPLACE) {
12
38
  return match[2];
13
39
  }
14
40
  }
15
- return null;
41
+ return rowsOfWrappedTable(text).find((row) => row.MARKETPLACE === MARKETPLACE)?.SOURCE ?? null;
16
42
  }
17
43
  function sourceWithoutFactoryPrefix(source) {
18
44
  return source.replace(/^(?:github|url):/, '');
package/mcp.pi.json CHANGED
@@ -6,8 +6,8 @@
6
6
  "description": "Postman workspaces, collections, specs, environments, mocks and monitors",
7
7
  "headers": {
8
8
  "X-Source": "postman-pi-plugin",
9
- "X-Plugin-Version": "0.5.0-rc.0",
10
- "User-Agent": "postman-pi-plugin/0.5.0-rc.0"
9
+ "X-Plugin-Version": "0.5.1",
10
+ "User-Agent": "postman-pi-plugin/0.5.1"
11
11
  }
12
12
  }
13
13
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@postman/postman-plugin",
3
- "version": "0.5.0-rc.0",
3
+ "version": "0.5.1",
4
4
  "description": "Postman's API engineering skills for coding agents: a Pi package, and an npx installer that sets up Claude Code, Codex, Cursor, Kimi Code, Factory Droid, OpenCode and Pi.",
5
5
  "type": "module",
6
6
  "license": "Apache-2.0",
@@ -1,27 +1,50 @@
1
1
  ---
2
2
  name: api-simulation
3
- description: Runs your code for real against stand-ins for the HTTP services it calls, served together as one environment on one local port or one deployed URL, whether that code is a service you're changing, a frontend, a test suite or a demo. Use when you add or change code that calls other services and need to run it, test it, prove a fix or reproduce an upstream failure (timeouts, 5xx, 409, 429) end to end, locally or in CI, without the real services because they're unreachable, need credentials, cost money, have side effects or don't exist yet. Also answers what happens to a request, or to the data it writes, when a service it calls goes down, is slow or returns errors ("what happens to a booking if the email service is down?"), by running each failure instead of reading the code. Applies even when nobody says mock or simulation. To give a frontend or another team a stand-in for an API, use api-mocking. Covers `postman simulation`.
3
+ description: Runs services, frontends, tests or demos against stand-ins for their HTTP dependencies, served as one environment locally or at a deployed URL. Use when you add or change code that calls other services and need to run it, test it, prove a fix or reproduce an upstream failure (timeouts, 5xx, 409, 429) end to end, locally or in CI, without the real services because they're unreachable, need credentials, cost money, have side effects or don't exist yet. Also answers what happens to a request, or to the data it writes, when a service it calls goes down, is slow or returns errors ("what happens to a booking if the email service is down?"), by exercising relevant failures and observing caller outcomes. Applies even when nobody says mock or simulation, and a `postman/simulations/` directory in the repository is itself a reason to load this before changing code it covers. To give a frontend or another team a stand-in for an API, use api-mocking. Covers `postman simulation`.
4
4
  ---
5
5
 
6
6
  # API Simulation
7
7
 
8
- A simulation serves several mocks as one environment, each reached by its
9
- routeKey: as a path prefix by default, or as an `x-mock-slug` header when the
10
- caller's paths can't change.
11
-
12
- Run your code against it rather than stubbing its HTTP client in tests or
13
- calling the real services. A stub skips the client, config, timeouts and
14
- error handling that will run in production; the real services need
15
- credentials and leave side effects. A simulation runs that code over real
16
- HTTP, the same way in CI, and its log shows what each dependency was asked.
17
-
18
- Do only what the task needs. If a `.sim.yaml` in `postman/simulations/`
19
- already covers it, run that one, and read the `<name>.context.md` beside it
20
- first: it holds the commands, the preconditions and the failures earlier runs
21
- hit, so reading it costs less than rebuilding any of that from the `.sim.yaml`
22
- and the handlers. One request through the changed path proves a change; build
23
- failure variants only when asked how the code fails or when it must survive a
24
- failure.
8
+ Simulation lets you control how dependencies behave and explore how your code
9
+ responds to different outcomes. This makes it easier to test assumptions
10
+ repeatedly without real calls changing data, spending money, or notifying
11
+ someone. Simulation context records those assumptions, the evidence supporting
12
+ them, and what remains unknown. As sessions build on this context, it helps
13
+ surface incorrect assumptions and avoid repeated investigation.
14
+
15
+ Together, the simulation and its context provide a place to explore changes
16
+ and identify what still needs validation against the real dependencies.
17
+
18
+ Check whether an existing simulation in `postman/simulations/` covers the
19
+ dependency interactions relevant to the task. Reusing its mocks and wiring
20
+ can avoid rebuilding an environment; extend it only where the behavior you
21
+ need to exercise is missing.
22
+
23
+ The `<name>.context.yaml` beside a simulation records those assumptions, their
24
+ supporting evidence, and unresolved questions so later sessions can build on
25
+ earlier findings. For example, a timeout from `/payments` does not establish
26
+ whether the payment failed or completed before the response arrived. The
27
+ context file will document what has been established about the real provider
28
+ and identify what still needs verification.
29
+
30
+ A simulation serves several mocks as one environment: by default behind a
31
+ proxy that answers for the dependencies' own hostnames, so the code under test
32
+ keeps the base URLs it ships with, or on one local port where each member is
33
+ reached by its routeKey.
34
+
35
+ Simulation adds value when the result depends on real HTTP behavior, such as
36
+ client configuration, serialization, timeouts or retries, and the real
37
+ dependencies are unavailable or unsuitable for the exercise. Stubbing the
38
+ HTTP client can bypass those behaviors. Choose verification that exercises
39
+ what matters to the task: an existing test may already answer a local logic
40
+ question, while a claim about the provider may need its contract or a real
41
+ service check. A simulation's log shows what each stand-in was asked.
42
+
43
+ Define the expected outcome and what observation would distinguish correct
44
+ from incorrect behavior, including side effects when relevant. A request
45
+ through the changed path shows that it ran; its response and resulting state
46
+ determine whether the expectation held. Choose boundary and failure cases
47
+ from the behavior changed and the consequences of getting it wrong.
25
48
 
26
49
  `postman simulation` needs CLI 1.70.0 or later (`postman --version`; an older
27
50
  CLI answers `postman simulation -h` with its general help).
@@ -52,45 +75,37 @@ The run is a server that never exits, so a foreground command never returns.
52
75
  Start it in the background, its log in a file:
53
76
 
54
77
  ```bash
55
- postman simulation run postman/simulations/checkout-dev.sim.yaml --port 4900 --output ndjson > sim.ndjson 2> sim.err &
78
+ postman simulation run postman/simulations/checkout-dev.sim.yaml --proxy --output ndjson > sim.ndjson 2> sim.err &
56
79
  ```
57
80
 
58
- `--port` is optional. It's up when `sim.ndjson` has a `listening` event; a
59
- start-up error is in `sim.err`. That event carries the bound port and each
60
- member's address, so take the base URLs from it rather than from the port you
61
- asked for. Stop it with
81
+ `--proxy` is how to run it unless something below rules it out: nothing about
82
+ the code changes for the run — not a base URL, not a config file, not an SDK's
83
+ built-in host — and because nothing the proxy answers for has to resolve, an
84
+ internal hostname with no DNS record works.
85
+
86
+ It's up when `sim.ndjson` has a `listening` event; a start-up error is in
87
+ `sim.err`, a busy proxy port among them. Stop it with
62
88
  `pkill -INT -f "simulation run .*<file>.sim.yaml"`. Nothing reloads: restart
63
89
  it after a mock edit.
64
90
 
65
- Point the caller at it without changing what the code does once deployed.
66
- Under path routing, swap only the host and keep the path:
67
- `https://catalog.internal.example.com/v2` becomes
68
- `http://127.0.0.1:4900/catalog/v2`; a dropped `/v2` is the usual cause of a
69
- `404`. A hard-coded base URL should read an environment variable with
70
- today's value as the default. If the code mustn't change at all, run it
71
- behind the proxy instead, below.
72
-
73
- ## Through a proxy, when no base URL can change
74
-
75
- `simulation run <file.sim.yaml> --proxy` puts the members behind a local
76
- forward proxy that answers for the dependencies' own hostnames, so the code
77
- keeps the base URLs it ships with. Any other host passes through to the real
78
- service. Reach for it when hostnames are hard-coded, built from config the
79
- change shouldn't touch, or owned by an SDK — and because nothing it answers
80
- for has to resolve, an internal hostname with no DNS record works.
81
-
82
91
  Read `postman simulation run --help` before driving it. The flags, what the
83
92
  caller's own process needs in order to reach the proxy, and what to do about a
84
93
  client that ignores it are all there and change with the CLI, so take them
85
94
  from the command rather than from here.
86
95
 
87
- Two things decide whether a proxied run proves anything. A member with no
88
- `origin:` in the `.sim.yaml` is never matched, so its traffic reaches the real
96
+ What decides whether a proxied run proves anything is `origin:`. A member with
97
+ none in the `.sim.yaml` is never matched, so its traffic reaches the real
89
98
  service and a run can come up having intercepted nothing — `simulation create`
90
99
  copies that field from the mock's manifest, so editing the manifest alone
91
100
  won't fix it ([reference/sim_yaml_schema.md](reference/sim_yaml_schema.md)).
92
- And conditions don't apply here: member `scenarios` are ignored, so failures
93
- need the one-port run.
101
+
102
+ ## Running it on one port instead
103
+
104
+ `--port` puts every member on one local port, each addressable by its routeKey,
105
+ with the code under test pointed at that port. Reach for it when a client has
106
+ no proxy setting to honour, and when you want to address a member yourself —
107
+ each one keeps a stable URL for the whole run, so a mock's control routes and
108
+ its individual responses are a curl away.
94
109
 
95
110
  ## Reading the log
96
111
 
@@ -98,8 +113,8 @@ A passing test command doesn't show the dependency path ran: read
98
113
  `sim.ndjson`. A member with no `request` events isn't wired or isn't on this
99
114
  path. Some answers come from the simulation itself and mean wiring, not code:
100
115
 
101
- - `404 Unknown simulation member`: a base URL lost its routeKey, or a client
102
- sent the wrong `x-mock-slug`.
116
+ - `404 Unknown simulation member`, on a one-port run: a base URL lost its
117
+ routeKey, or a client sent the wrong `x-mock-slug`.
103
118
  - `501`, or a generated mock's `404 Endpoint not defined`: a call the mock
104
119
  doesn't model yet.
105
120
  - `502 Proxy error`: the member's process died, usually an async throw in a
@@ -108,12 +123,20 @@ path. Some answers come from the simulation itself and mean wiring, not code:
108
123
 
109
124
  ## When a dependency goes down
110
125
 
111
- Run it rather than reason from the code: each way of going down takes a
112
- different path through the caller, and a stub skips the client code that
113
- picks the path. Fail the one dependency in each shape that matters, a run
114
- each, with the others answering normally:
126
+ Choose failures from the outcome the user needs to understand. A failure
127
+ before a write, a timeout after a write may have completed, and a failure
128
+ during compensation can expose different risks: lost work, duplicate writes
129
+ or unreleased resources. Read the caller to identify relevant paths, then
130
+ exercise the ones whose runtime behavior remains uncertain.
115
131
 
116
- - **Unreachable:** point its base URL at a port nothing listens on.
132
+ Isolating one failure helps attribute the result. Combine failures or model
133
+ partial completion when the question depends on their interaction. The
134
+ injection must represent the event being investigated: delaying a response
135
+ alone does not establish whether a write completed before the timeout.
136
+ Examples of available techniques:
137
+
138
+ - **Unreachable:** point its base URL at a port nothing listens on. This one
139
+ needs the one-port run, since under `--proxy` the base URLs are the point.
117
140
  - **Hanging:** give its member a `latency` condition longer than the
118
141
  client's timeout. If the client sets none, that is the finding: Node's
119
142
  `fetch` waits about five minutes, so say so rather than wait it out.
@@ -131,19 +154,24 @@ stock reserved and never released.
131
154
 
132
155
  ## Its context document
133
156
 
134
- Every simulation is created with a `<name>.context.md` beside it, holding what
135
- running this one has taught: the commands that start it and prove it's up, the
136
- preconditions and what breaks when one fails, what it takes for granted about
137
- the dependencies themselves, the data seeded to test with, the failures earlier
138
- runs already hit, and what a passing run still doesn't prove. The `.sim.yaml` says what the simulation is, not how it behaves, so
139
- this is the file that saves a session the search. A new one is mostly
140
- `@todo`s, which are the questions a first real run answers.
141
-
142
- Every line passes one test — the repo doesn't already state it — so it stays
143
- about a screenful, and a claim carries the date and the run that established
144
- it, because the log it came from isn't committed. Its sections and conventions
145
- are in [reference/context_file.md](reference/context_file.md); read that
146
- before writing it back.
157
+ Every simulation is created with a `<name>.context.yaml` beside it, holding
158
+ what the code is believed to rely on about the dependencies it calls — who the
159
+ caller is taken to be, which plan or permission is assumed, the order calls go
160
+ in, what gets retried, the shape of the data — and what each belief rests on.
161
+ The `.sim.yaml` says what the simulation is, not what the services it stands in
162
+ for do, so this is the record that saves a session the search.
163
+
164
+ Read relevant claims before investigating the dependencies, and assess whether
165
+ their evidence still applies to this task. Write what you established back
166
+ into the document before you finish. Each claim carries a `status`,
167
+ the `reason` the evidence adds up to it, the `verification` that was done and
168
+ what it showed, and a `source` to re-check it against, so the next session can
169
+ tell a claim a contract promises from one a request actually demonstrated.
170
+ Preserve existing claims and their evidence when updating the file, including
171
+ unresolved assumptions and historical findings. Its fixed schema and guidance
172
+ on evidence and reuse are in
173
+ [reference/context_file.md](reference/context_file.md); read that before
174
+ writing to it.
147
175
 
148
176
  ## Keeping it
149
177
 
@@ -173,16 +201,21 @@ first. It fails on a pulled member, which keeps its owner's `id:`; copy it to
173
201
 
174
202
  ## Before you say it works
175
203
 
176
- Claim nothing until what you're testing actually ran: a service printed
177
- that it's listening and answered your request, or the frontend or test
178
- suite finished, and its calls are in the log. If its start command exited
179
- or hung, the run proved nothing; say so and why. Say what a pass proves,
204
+ Claim a simulation passes only after the code under test ran and you observed
205
+ the expected response or state change. Use its calls in the log to confirm
206
+ the dependency path was exercised; for injected failures, use the caller
207
+ outcomes described above. If startup failed or the relevant path never ran,
208
+ report that limitation. Say what a pass proves,
180
209
  your code against these stand-ins rather than the real services, what still
181
210
  needs the real service before release, and where each mock came from,
182
211
  including when you skipped the owner's.
183
212
 
184
- Then write the run back into `<name>.context.md`, so the next session starts
185
- where this one ended: the `@todo`s it answered, whatever it found wrong, a
186
- precondition you had to discover, what broke and why, and which members served
187
- or stayed silent — the last is a line every run adds, so a member silent across
188
- several runs reads as a pattern rather than a one-off.
213
+ Then write what you established back into `<name>.context.yaml`, so the next
214
+ session starts where this one ended. Add or update claims that help a later
215
+ session reuse the findings. For each claim the run settled or contradicted,
216
+ update its `status`, the `verification` that was done and the `source` to
217
+ re-check it against. Scope what you claim to what you actually
218
+ exercised — "held in the scenarios run" rather than "works" — since the record
219
+ is read later as evidence, and the two don't mean the same thing.
220
+ [reference/context_file.md](reference/context_file.md) has what each field is
221
+ for.
@@ -1,159 +1,193 @@
1
- # `<name>.context.md`
1
+ # `<name>.context.yaml`
2
2
 
3
- The handoff document beside a simulation: `postman/simulations/checkout.sim.yaml`
4
- gets `checkout.context.md`, and a foldered simulation keeps both in its folder.
5
- `simulation create` and the Postman app write it from what they already know,
3
+ The handoff record beside a simulation: `postman/simulations/checkout.sim.yaml`
4
+ gets `checkout.context.yaml`, and a foldered simulation keeps both in its folder.
5
+ `simulation create` and the Postman app write it from a fixed template,
6
6
  `simulation update` renames it with the simulation, and `simulation delete`
7
- removes it. An existing one is never overwritten, because it holds what earlier
8
- sessions learned.
7
+ removes it.
9
8
 
10
- It is prose for whoever works here next, human or agent. Nothing parses it, so
11
- there is no schema to satisfy — but the generated headings and the
12
- `<!-- begin:observations -->` markers are what a reader and the next write
13
- expect to find, so keep them.
9
+ **Update the existing record without discarding prior claims or evidence.**
10
+ Replacing it with a fresh template loses findings and unresolved assumptions
11
+ while looking complete.
14
12
 
15
- Keep it to the generated sections plus `## What didn't work`, in Markdown,
16
- under about a screenful, so the line that matters is the one a reader lands on
17
- rather than one they have to search for.
13
+ It carries what is believed about the services this simulation stands in for,
14
+ and what each belief rests on. **api-simulation** is how a simulation is run,
15
+ read and judged — its commands running clean is not yet the work being done.
18
16
 
19
- ## What the generated file contains
17
+ ## Its shape
20
18
 
21
- A fresh file states the simulation's name, the date, its members and the hosts
22
- they stand in for, and the run command for this simulation's own path.
23
- Everything needing someone who has actually run it is left as an `@todo` HTML
24
- comment, so a half-filled file reads as half-filled instead of as fact.
19
+ The whole file is YAML under a fixed key schema. The key names and the `status`
20
+ vocabulary are fixed; the template carries each key's field schema in the
21
+ comments above it. A `null` is something nobody has established yet.
25
22
 
26
- | Section | Holds |
23
+ | Key | Holds |
27
24
  | --- | --- |
28
- | Header | Generation date and what the mocks came from. |
29
- | `## Purpose` | What the simulation stands in for and what work it unblocks. Generated as the member list and a pointer to the handlers; needs hindsight, so it is the last thing to rewrite. |
30
- | `## Execution` | The commands, in order: start the simulation, start the service pointed at it, prove it is up, count what got called, stop. The generated block leaves the service's own command, the proof request, the port and the log path as `@todo`. |
31
- | `## Assumptions` | Every precondition someone had to discover, what the simulation takes for granted about the dependencies' own behaviour, the seeded data worth reusing, and what the simulation cannot reproduce. Each is a dated claim, the consequence of it not holding, and how it was established. |
32
- | `## Observations` | One dated line per run, newest first, between the `begin`/`end` markers: which members took traffic and which took none. A summary, not a request log, and a failure goes in `## What didn't work` instead. |
33
- | `## What didn't work` | Failures this simulation has already hit, a dated line each: what broke, the signature you'd recognize it by, where it surfaced, and the cause. Not generated — add the heading at the end, after `## Observations`, the first time a run gives you one. |
34
-
35
- `## Observations` and `## What didn't work` both accrete, so a run is visible
36
- in the file after the next one; `## Purpose`, `## Execution` and
37
- `## Assumptions` are rewritten in place, since they describe the simulation as
38
- it stands rather than a run. Each accreting section is bounded: `## What didn't
39
- work` keeps the ten most recent, a line staying until a run shows it fixed, and
40
- `## Observations` the five most recent runs. Past that the oldest go.
41
-
42
- A run that keeps its line is how a pattern becomes visible — a member silent
43
- for three runs running is worth more than the same fact seen once.
44
-
45
- Don't restate what the generated file already says: its `## Assumptions` ships
46
- with the ones that hold for every simulation.
47
-
48
- ## What earns a line
49
-
50
- Write what running it taught you, and only that. Each line passes one test:
51
- the repo doesn't already state it. That leaves five kinds of thing, and they
52
- are the file's whole value:
53
-
54
- - **A precondition you had to discover** — the setting, the service, the
55
- cleanup a run needs before it works at all. In `## Assumptions`.
56
- - **A failure and its cause**, with where it surfaced, so the next session
57
- recognizes it instead of debugging it again. In `## What didn't work`.
58
- - **What the run takes for granted about a dependency itself** — that an
59
- operation is idempotent, that a timeout means nothing committed, what order
60
- events arrive in. A mock answers these whether or not anyone checked, so its
61
- answer is a belief your code now relies on. In `## Assumptions`.
62
- - **What the simulation can't reproduce, and what nobody has settled**, so a
63
- passing run is read for what it actually proves. In `## Assumptions`.
64
- - **What the last run served**: which members took traffic, which stayed
65
- silent, and why. In `## Observations`.
66
-
67
- For anything the repo does state — routes and request shapes, valid actions,
68
- auth mechanics, each mock's hosts, config flags — name the file that holds it,
69
- the way the generated `## Purpose` points at the handlers, and let the reader
70
- go there. One source stays true; a copy here drifts, and this file gets
71
- committed, so a named file is also how a credential stays out of it.
72
-
73
- Write statements that stay true between runs. Counts, timings and coverage
74
- belong to the run that produced them, so leave them in the log and keep the
75
- pattern here: "every member served except `workspace`" rather than a tally.
76
-
77
- ## How a line carries its evidence
78
-
79
- The log a claim came from isn't committed, so the line is all the next session
80
- gets. Date every claim in `## Assumptions` and `## What didn't work`, and say
81
- where it came from when a run didn't show it: `(observed 2026-10-22)` for what
82
- a run established, `(reasoned 2026-10-22, src/payments/client.ts)` for what was
83
- read rather than run.
84
-
85
- A failure also carries the signature the next session will recognize it by —
86
- the error text, the status, the member that sent it — since that is what
87
- someone matches a new failure against. Quote the error itself, never a
88
- response body. For the rest, name the committed file that holds it: the
89
- handler that threw, the mock example captured from the real service, the test
90
- that reproduces it. Not the run's log, which is wherever that run redirected
91
- output and which the next run overwrites.
92
-
93
- A good `## Assumptions` is a claim, its consequence and its date; a good
94
- `## Observations` is a few lines; a good `## What didn't work` is one line per
95
- failure:
96
-
97
- ```markdown
98
- ## Assumptions
99
-
100
- - **`payments` treats a retried charge as idempotent on `Idempotency-Key`.**
101
- If it doesn't, the retry path charges twice. Nobody has settled it and the
102
- mock answers as though it does. (reasoned 2026-10-22, the mock's handler)
103
- - **Redis holds no `EVENT_BATCH` key when a run starts.** A leftover one
104
- crashes the aggregation flush before any request is sent. (observed
105
- 2026-10-22)
106
-
107
- <!-- begin:observations -->
108
- 2026-10-22: every member served except `workspace`, which the service never
109
- calls in development, and `god`, which models no endpoints.
110
- 2026-10-18: same, and `payments` served only `POST /charges`.
111
- <!-- end:observations -->
112
-
113
- ## What didn't work
114
-
115
- - 2026-10-22: the aggregation flush crashed on start —
116
- `TypeError: batch.map is not a function` in `src/events/flush.ts`, from a
117
- stale Redis `EVENT_BATCH` key an earlier run left behind. Not a simulation
118
- error, so the log shows nothing.
119
- - 2026-09-30: `payments` answered `501` for `POST /refunds` — the mock models
120
- no refund path, so a run that reaches it proves nothing about refunds.
25
+ | `purpose` | `stands_in_for` and `unblocks` — what the simulation stands in for and what work it unblocks. Record the intended purpose when known and refine it as you learn, distinguishing intended outcomes from demonstrated results. The members it serves, their route keys and the hosts they answer for are in the `.sim.yaml` beside it. |
26
+ | `assumptions` | What the code relies on about a dependency's behaviour. One claim per entry: `id`, `status`, `claim`, `reason`, `verification`, `source`, `updated_at`. |
27
+ | `fingerprints` | What the claims were written against — `id`, `date`, `commit`, `cli`. |
28
+
29
+ Everything else about this simulation lives where it already is: the members and
30
+ their origins in the `.sim.yaml`, the handlers in `postman/mocks/`, and how to
31
+ run any of it in **api-simulation**. Use this record to connect those details
32
+ to the assumptions and findings a later session needs.
33
+
34
+ Keep an entry when it helps a later session make a decision without repeating
35
+ the investigation, even if supporting details already exist in the repo.
36
+ For routes, request shapes, auth or config, link the file that holds them and
37
+ record the conclusion, evidence and uncertainty relevant to the caller.
38
+ Avoid copying details that can drift, and keep credentials out of this
39
+ committed record.
40
+
41
+ ## What an assumption is
42
+
43
+ A claim about how a dependency behaves, which the code under test relies on:
44
+ what the caller is taken to be, which plan or permission is assumed, the order
45
+ calls go in, what gets retried, the shape of the data. A mock answers these
46
+ whether or not anyone checked, so its answer is a belief your code now carries.
47
+
48
+ For example, checkout may retry a payment after a timeout without knowing
49
+ whether the first attempt charged the customer. This illustrative entry
50
+ records the provider assumption that makes that retry safe; the paths and
51
+ run reference are examples, not evidence from this repository.
52
+
53
+ ```yaml
54
+ - id: payment-retry-does-not-double-charge
55
+ status: unknown
56
+ claim: Retrying a payment for the same unchanged cart with identical payment
57
+ details and the same idempotency key within 24 hours creates at most one
58
+ charge at the payment provider, even if the first response times out.
59
+ reason: Checkout relies on this guarantee to retry without double-charging
60
+ the customer. The stand-in deduplicates payments by key, but that behavior
61
+ was configured by us; a passing simulation does not establish the
62
+ provider's retention window or handling of a retry after a timeout.
63
+ verification: On 2026-10-28, ran the checkout timeout test. The stand-in
64
+ recorded a successful charge, then delayed its response past the caller's
65
+ timeout. Checkout retried with the same payment details and key, and the
66
+ stand-in returned the original payment ID with one charge in its state.
67
+ This demonstrates caller key reuse in this scenario. The provider's
68
+ idempotency contract has not been checked; confirm its scope and retention
69
+ window, including retries after an ambiguous outcome, before relying on
70
+ this assumption for real payments.
71
+ source: src/checkout/submit-payment.ts; postman/mocks/payments/handler.js;
72
+ tests/checkout/payment-timeout.test.ts; runs/cart-payment-timeout-001
73
+ updated_at: "2026-10-28"
121
74
  ```
122
75
 
123
- ## Working with it
124
-
125
- Read it before starting. An observed claim holds until your run contradicts
126
- it; a reasoned one is a hypothesis, so check it, cheaply, when your run depends
127
- on it. Check the assumptions and anything recorded about what a run doesn't
128
- prove before concluding that something works. A claim this run contradicts is
129
- a finding to report, not to quietly route around.
130
-
131
- What it already answers, it answers: `## Execution` holds commands that ran,
132
- and `## Assumptions` holds preconditions someone already established, so both
133
- are usable as they stand rather than as a starting point to rebuild from. The
134
- code you are about to change still needs reading; the setup around it is what
135
- this file is for. A session that reads the document and then repeats the
136
- search it was meant to replace has paid for it twice.
137
-
138
- Then leave it truer than you found it:
139
-
140
- - Replace the `@todo`s your run can answer, and delete each one you replace.
141
- - Correct what you found wrong. Say what it is now, not what it used to be.
142
- - Add the precondition you had to discover, or the thing the simulation turned
143
- out not to reproduce, as another dated claim-and-consequence pair.
144
- - Add what broke and why to `## What didn't work`, and delete the lines there
145
- your run showed fixed.
146
- - Add this run's summary to the top of the `## Observations` block, read from
147
- the simulation's log, and drop the oldest line past five.
148
-
149
- A run that only confirms the file still adds its `## Observations` line.
150
-
151
- ## Rules
152
-
153
- - Leave another session's assumptions and limits standing unless your run
154
- contradicted one: not exercised is not the same as not true.
155
- - Keep this file for how the simulation behaves, and let the `.sim.yaml`
156
- speak for what it is.
157
- - Rewrite `## Purpose` when the simulation's scope genuinely changed.
158
- - Prefer deleting a line to adding one. Everything here is something the next
159
- session must either trust or re-check.
76
+ One claim per entry. A sentence holding two claims is two entries, because
77
+ evidence can settle one and leave the other standing.
78
+
79
+ ### `status`
80
+
81
+ | Value | Means | For example |
82
+ | --- | --- | --- |
83
+ | `supported` | Evidence supports the claim, within a stated scope. | The provider guarantees idempotency for 24 hours. |
84
+ | `contradicted` | Evidence provides a counterexample. | A timed-out request did create an order. |
85
+ | `unknown` | Available evidence cannot establish or reject it. | Key scope across token refresh is unspecified. |
86
+ | `conflicting` | Applicable sources disagree. | Documentation promises immediate revocation; a real probe observes continued access. |
87
+
88
+ `supported` is the one to be careful with: support comes with a scope, so the
89
+ claim says what that scope is. Name the operation, relevant conditions and
90
+ window the evidence covers. Narrow an overbroad claim before marking it
91
+ `supported`; a check at one point in a window does not establish behavior
92
+ throughout it.
93
+
94
+ `contradicted` and `conflicting` are findings to report, not states to route
95
+ around. A contradiction says the code relies on something untrue; a conflict
96
+ says two sources disagree and nobody has settled which governs — often a
97
+ contract promising one thing while the running service does another.
98
+
99
+ ### `reason`, `verification` and `source`
100
+
101
+ Three different jobs, and keeping them apart is what makes a claim checkable.
102
+
103
+ `reason` is the judgment: why the evidence adds up to this status. It is the
104
+ field that earns its place on a `conflicting` or `unknown` claim, where the
105
+ status alone does not say what is unresolved.
106
+
107
+ `verification` is what was actually done and what it showed — a contract read,
108
+ a request made and its response, a handler inspected. Include when the
109
+ verification occurred and the revision or environment it covered, using a
110
+ fingerprint entry where applicable. Name what is still
111
+ planned or blocked and what would settle it, and say when a piece of evidence
112
+ has gone stale and why, since stale evidence stays as history but cannot
113
+ establish a current status.
114
+
115
+ Match the claim to the kind of evidence. A mock handler shows what the
116
+ stand-in models; a simulated run shows how the caller behaved under that
117
+ model. Neither independently establishes the real dependency's behavior.
118
+ Use provider evidence for that: distinguish a published contract's promise
119
+ from behavior observed against the real service, and retain the conditions
120
+ and scope of each. If only the mock supports a provider assumption, that
121
+ assumption remains `unknown` even when the caller test passes.
122
+
123
+ `source` is where to go and re-check it, so the next session can audit the
124
+ claim rather than take it on trust. Any of:
125
+
126
+ - a file and line range in this repo, where the code shows what it relies on;
127
+ - a request that was actually made and what came back — the target environment
128
+ (stand-in or real service), sequence and status codes, with a retrievable
129
+ test or run reference where available;
130
+ - the dependency's own code, or its published contract, read for the endpoint
131
+ in question.
132
+
133
+ A claim is only as good as what it points at, so `source` carries the specific
134
+ thing, not its category: `api/policies/hasAccess.js:12-115` rather than "read
135
+ the code". A claim with nothing to point at is `unknown`.
136
+
137
+ Where two sources disagree, `verification` says what each one showed and
138
+ `status` is `conflicting` — that is the state a contract promising one thing
139
+ while the running service does another belongs in.
140
+
141
+ ### `updated_at`
142
+
143
+ The day the entry was last written, `YYYY-MM-DD`. Set it when you add a claim,
144
+ and set it again whenever you change one — a new status, a fresh piece of
145
+ verification, a reworded claim.
146
+
147
+ This date tracks record maintenance. Rewording a claim does not refresh its
148
+ evidence: judge that from the date, revision and environment in `verification`
149
+ and its referenced fingerprint. Re-check when relevant behavior or conditions
150
+ have changed, or when the evidence's applicability is unclear.
151
+
152
+ Nothing enforces this. `simulation create` writes an empty list and no command
153
+ edits entries, so the field is only as true as the session that last wrote it.
154
+
155
+ ## Where claims come from
156
+
157
+ Evidence first, claim second. Make the run or read the code, then write down
158
+ what it establishes — including the parts nobody had written down before, which
159
+ are the valuable half: a call order you did not expect, a dependency answering
160
+ something the code does not handle, a member never called at all.
161
+
162
+ Going the other way — writing the claim and then looking for something to back
163
+ it — is how a guess ends up marked `supported`. A claim written ahead of any
164
+ evidence is fine; it is `unknown` until something points at it.
165
+
166
+ ## Fingerprints
167
+
168
+ What the claims were written against: `git rev-parse HEAD` and
169
+ `postman --version`. Append an entry when either differs, and say so when the
170
+ tree is dirty — a sha alone reads as a clean checkout.
171
+
172
+ Tie each verification to the fingerprint it used. Appending a new fingerprint
173
+ does not revalidate older claims. A changed commit or CLI is a reason to
174
+ assess whether relevant behavior changed; it does not by itself invalidate
175
+ all prior evidence.
176
+
177
+ ## Reading it before you start
178
+
179
+ Read the claims relevant to the task before repeating dependency research.
180
+ Compare their scope and evidence with the code, configuration and provider
181
+ conditions your work depends on. Reuse findings that still apply, and inspect
182
+ the code you are changing to identify assumptions the change may affect.
183
+
184
+ Treat `supported` as conditional on that applicability, not as a permanent
185
+ guarantee. Revisit affected claims when relevant conditions change or their
186
+ evidence no longer establishes the current behavior. If current support is
187
+ unresolved, mark the claim `unknown` and preserve the prior evidence with the
188
+ reason it no longer settles the question. An `unknown` or `conflicting` claim
189
+ is worth investigating when the task depends on it; record what would settle
190
+ it if verification remains blocked.
191
+
192
+ Preserve unrelated claims and their history. A claim not exercised in this
193
+ session is not thereby contradicted or stale.
@@ -26,7 +26,7 @@ since the file is what declares the hostnames.
26
26
  `simulation create` writes each `path:` relative to the repository root, the
27
27
  directory holding `postman/`, and puts the simulation in a folder of its own
28
28
  (`postman/simulations/checkout-dev/checkout-dev.sim.yaml`) so its
29
- `<name>.context.md` sits beside it; the app records that document in a
29
+ `<name>.context.yaml` sits beside it; the app records that document in a
30
30
  `context:` ref. Reading is broader than writing — a path relative to
31
31
  `postman/simulations/`, to the file, absolute or `~/`, naming the folder or
32
32
  the manifest inside it, all resolve — and an unresolvable one names every