@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.
package/dist/hosts/factory.js
CHANGED
|
@@ -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
|
|
32
|
+
return installedPlugins(text).includes(`${PLUGIN_ID} [${SCOPE}]`);
|
|
7
33
|
}
|
|
8
34
|
function marketplaceSource(text) {
|
|
9
|
-
for (const line of text.split(
|
|
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.
|
|
10
|
-
"User-Agent": "postman-pi-plugin/0.5.
|
|
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.
|
|
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
|
|
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
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
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 --
|
|
78
|
+
postman simulation run postman/simulations/checkout-dev.sim.yaml --proxy --output ndjson > sim.ndjson 2> sim.err &
|
|
56
79
|
```
|
|
57
80
|
|
|
58
|
-
`--
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
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
|
-
|
|
88
|
-
|
|
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
|
-
|
|
93
|
-
|
|
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
|
|
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
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
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
|
-
|
|
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.
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
this is the
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
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
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
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
|
|
185
|
-
where this one ended
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
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.
|
|
1
|
+
# `<name>.context.yaml`
|
|
2
2
|
|
|
3
|
-
The handoff
|
|
4
|
-
gets `checkout.context.
|
|
5
|
-
`simulation create` and the Postman app write it from
|
|
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.
|
|
8
|
-
sessions learned.
|
|
7
|
+
removes it.
|
|
9
8
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
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
|
-
|
|
16
|
-
|
|
17
|
-
|
|
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
|
-
##
|
|
17
|
+
## Its shape
|
|
20
18
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
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
|
-
|
|
|
23
|
+
| Key | Holds |
|
|
27
24
|
| --- | --- |
|
|
28
|
-
|
|
|
29
|
-
|
|
|
30
|
-
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
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
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
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.
|
|
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
|