@postman/postman-plugin 0.2.2 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/source.js CHANGED
@@ -5,15 +5,25 @@ export const GIT_URL = `https://github.com/${REPO}.git`;
5
5
  export const BRANCH = 'main';
6
6
  /** Unpinned, so `pi update` moves it with each `latest` release; test/pi-package.test.js checks the name. */
7
7
  export const PI_SOURCE = 'npm:@postman/postman-plugin';
8
- /** Must stay byte-identical to the shim in opencode/README.md; test/routes.test.js enforces it. */
8
+ /**
9
+ * This repo's default branch, whose root package.json names the OpenCode entrypoint. Not a `#semver:` range: npm reads a
10
+ * version from any tag ending in one, so the installer's `@postman/postman-plugin@<version>` tags would match it too.
11
+ */
12
+ export const OPENCODE_SPEC = `github:${REPO}`;
13
+ /** The first OpenCode release of each major that installs `OPENCODE_SPEC`, found by bisecting the harness in opencode/scripts/test-plugin-add.js. */
14
+ export const OPENCODE_MINIMUM = { 1: [1, 14, 33], 2: [2, 0, 4] };
15
+ /** Pre-`plugin add` installs: a clone of the repo plus this one-line loader file. Must stay byte-identical to what those installs wrote, or the installer stops recognizing them. */
9
16
  export const OPENCODE_SHIM = "export { default } from '../postman-plugin/opencode/src/index.ts';\n";
10
17
  /** Pinned: this third-party CLI writes Kimi's plugin store for us, and an unpinned npx would run whatever is latest. */
11
18
  export const PLUGINS_CLI = 'plugins@1.3.4';
12
19
  // A git transport (not `file://`, which names a local path), optional user-info, then
13
20
  // GitHub's host and its `/`, or the scp-style `git@github.com:` form with no scheme.
14
- const GITHUB_PREFIX = /^(?:(?:https?|ssh|git|git\+ssh|ssh\+git|git\+https):\/\/)?(?:[^@/]+@)?github\.com[:/]/,
15
- // User-info in a URL, or before an scp-style `host:path`; the conventional `git@` is kept.
16
- URL_USER_INFO = /^([a-z][a-z+.-]*:\/\/)(?!git@)[^@/]+@/i, SCP_USER_INFO = /^(?!git@)[^@/:]+@(?=[^/:]+:)/;
21
+ const GITHUB_PREFIX = /^(?:(?:https?|ssh|git|git\+ssh|ssh\+git|git\+https):\/\/)?(?:[^/]*@)?github\.com[:/]/,
22
+ // User-info in a URL, or before an scp-style `host:path`. Greedy, because a password may hold an unescaped `@` and a
23
+ // URL's host starts after the last one.
24
+ URL_USER_INFO = /^([a-z][a-z+.-]*:\/\/)([^/?#]*)@/i, URL_USER_INFO_IN_TEXT = /([a-z][a-z+.-]*:\/\/)([^/?#\s]*)@/gi, SCP_USER_INFO_IN_TEXT = /(^|[\s"'`(=])([^/:\s"'`(=]*)@(?=[^@/:\s]+:)/gm, SCP_USER_INFO = /^()([^/:]*)@(?=[^@/:]+:)/,
25
+ // The conventional `git@` names no credential and is kept.
26
+ withoutUserInfo = (match, before, info) => (info === 'git' ? match : before);
17
27
  function normalize(source) {
18
28
  return source.trim().toLowerCase()
19
29
  .replace(GITHUB_PREFIX, '')
@@ -26,5 +36,9 @@ export function isSameRepo(source, repo) {
26
36
  }
27
37
  /** Strips a URL's user-info, where a token would be, so a source can be printed. */
28
38
  export function redact(source) {
29
- return source.replace(URL_USER_INFO, '$1').replace(SCP_USER_INFO, '');
39
+ return source.replace(URL_USER_INFO, withoutUserInfo).replace(SCP_USER_INFO, withoutUserInfo);
40
+ }
41
+ /** Strips the user-info of every URL in a command's output, which can echo a spec it was given. */
42
+ export function redactText(text) {
43
+ return text.replace(URL_USER_INFO_IN_TEXT, withoutUserInfo).replace(SCP_USER_INFO_IN_TEXT, withoutUserInfo);
30
44
  }
package/dist/system.js CHANGED
@@ -2,6 +2,7 @@ import { spawn } from 'node:child_process';
2
2
  import fs from 'node:fs/promises';
3
3
  import os from 'node:os';
4
4
  import path from 'node:path';
5
+ import { redact } from './source.js';
5
6
  // A missing file, or a path through something that isn't a directory. Anything else,
6
7
  // such as a file that exists but can't be read, must not pass for "absent".
7
8
  const ABSENT = ['ENOENT', 'ENOTDIR'];
@@ -30,8 +31,9 @@ function execute(file, args, env) {
30
31
  child.on('close', (code) => resolve({ code: code ?? 1, stdout, stderr }));
31
32
  });
32
33
  }
34
+ /** Formats a command for logs and failure messages, with the user-info of any URL in it redacted. */
33
35
  export function formatCommand(command, args) {
34
- return [command, ...args].map((part) => (/^[\w@./:=~-]+$/.test(part) ? part : JSON.stringify(part))).join(' ');
36
+ return [command, ...args].map((part) => redact(part)).map((part) => (/^[\w@./:=~-]+$/.test(part) ? part : JSON.stringify(part))).join(' ');
35
37
  }
36
38
  export function createSystem({ dryRun = false, log = (line) => console.log(line) } = {}) {
37
39
  const env = process.env, platform = process.platform;
@@ -85,6 +87,17 @@ export function createSystem({ dryRun = false, log = (line) => console.log(line)
85
87
  throw error;
86
88
  }
87
89
  },
90
+ async readDir(dir) {
91
+ try {
92
+ return await fs.readdir(dir);
93
+ }
94
+ catch (error) {
95
+ if (ABSENT.includes(error.code ?? '')) {
96
+ return [];
97
+ }
98
+ throw error;
99
+ }
100
+ },
88
101
  async probe(command, args) {
89
102
  return execute(await resolve(command), args, env);
90
103
  },
@@ -108,6 +121,12 @@ export function createSystem({ dryRun = false, log = (line) => console.log(line)
108
121
  await fs.rm(file, { recursive: true, force: true });
109
122
  }
110
123
  },
124
+ async rename(from, to) {
125
+ log(` rename ${from} ${to}`);
126
+ if (!dryRun) {
127
+ await fs.rename(from, to);
128
+ }
129
+ },
111
130
  log
112
131
  };
113
132
  }
@@ -3,7 +3,7 @@ You have the Postman plugin.
3
3
 
4
4
  Before responding to any non-trivial API engineering task — designing, implementing, mocking, testing, monitoring, documenting, or deploying an API or Postman Flow — load the `postman:api-engineer` skill and follow it. It is the default entry point and routes to the specific postman skills from there. Pure questions and trivial one-line edits don't need it.
5
5
 
6
- When the intent is already specific, you may enter directly into the relevant skill instead: `postman:bootstrap` (link this repo to a Postman workspace), `postman:api-mocking`, `postman:api-testing`, `postman:api-monitoring`, `postman:flows`, `postman:ci-integration`, `postman:api-discovery`, `postman:ai-readiness`, `postman:performance-testing`, `postman:api-documentation`.
6
+ When the intent is already specific, you may enter directly into the relevant skill instead: `postman:bootstrap` (link this repo to a Postman workspace), `postman:api-mocking`, `postman:api-testing`, `postman:api-monitoring`, `postman:flows`, `postman:ci-integration`, `postman:api-discovery`, `postman:ai-readiness`, `postman:performance-testing`, `postman:api-documentation`, `postman:variables` (where a value or secret belongs and how to reference it).
7
7
 
8
8
  If you were dispatched as a subagent to execute a specific task, ignore this block — `postman:api-engineer` governs the orchestrating session, and it already shaped your dispatch.
9
9
 
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.2.2",
10
- "User-Agent": "postman-pi-plugin/0.2.2"
9
+ "X-Plugin-Version": "0.3.0",
10
+ "User-Agent": "postman-pi-plugin/0.3.0"
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.2.2",
3
+ "version": "0.3.0",
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",
@@ -116,13 +116,29 @@ current `postman login` session, in that order.
116
116
 
117
117
  ## After discovery: reusing what was found
118
118
 
119
- `dependency add <type> <nameOrId>` formally adds a collection, environment,
120
- or mock found in another workspace as a dependency of the current one —
121
- the step after `search` finds something worth reusing (e.g., feeding
122
- `application test`'s contract matching), rather than copying it in by hand. It
123
- takes a Postman entity ID. If the Context Graph identifies a service or API to
124
- reuse, locate its collection with `search` first, then pass that entity ID to
125
- `dependency add`.
119
+ Finding another service's workspace does not make it the workspace for the
120
+ current repository. `workspace pull` is for working on the service that owns
121
+ that workspace, from that service's repository. If the service is only a
122
+ dependency, or the user only wants to inspect one of its resources, keep the
123
+ current repository connected to its own workspace and use the dependency
124
+ feature instead.
125
+
126
+ Use `postman dependency add <type> <name-or-id>` to declare and download the
127
+ selected resource rather than copying it by hand or pulling the other
128
+ service's entire workspace. Supported types are `collection`, `environment`,
129
+ and `mock`. Pass the entity ID returned by `search`, or use the entity name;
130
+ when resolving a name in a specific workspace, add `--workspace
131
+ <workspace-id>`. If the Context Graph identifies only a service or API, locate
132
+ the concrete entity with `search` first.
133
+
134
+ Dependencies are recorded in `.postman/resources.yaml` and downloaded under
135
+ `postman/.dependencies/`, so the copy works locally straight away. Use
136
+ `postman dependency list` to inspect declarations, `postman dependency
137
+ install` to download declared dependencies after a clone or in CI, `postman
138
+ dependency update` to refresh them from Postman, and `postman dependency
139
+ remove <name-or-id>` to delete a declaration and its downloaded files. The
140
+ `list`, `install`, `update`, and `remove` commands accept `--type <type>`;
141
+ `install` and `update` can also target one declared dependency by name.
126
142
 
127
143
  ## Reference
128
144
 
@@ -10,11 +10,25 @@ API documentation can be done in two predominant ways:
10
10
  1. through a Postman collection,
11
11
  2. with an OpenAPI spec
12
12
 
13
- It is recommended to create both. They serve different, complementary use cases, and it takes only one command to convert from one to another. Start with creating a Postman collection in v3 format, **collection-schema-v3**.
14
- Postman collections are very human-friendly and offer other capabilities like creating an API mock, monitor, SDK, or spec.
13
+ It is recommended to create both. They serve different, complementary use
14
+ cases, and the Postman CLI can generate one from the other. Use the artifact
15
+ that represents the current source of truth rather than recreating its
16
+ operations by hand. Postman collections are human-friendly and enable other
17
+ capabilities such as mocks, monitors, SDKs, and specifications. Collections
18
+ created or edited directly on disk use the v3 format described by
19
+ **collection-schema-v3**.
15
20
 
16
21
  Specs are vendor-neutral, stay in your repo, and can be linted against governance rules (if any) set by your organization.
17
22
 
23
+ ## Generate a collection from a specification
24
+
25
+ When an OpenAPI specification already defines the contract, generate a Postman
26
+ collection from it in one command:
27
+
28
+ ```bash
29
+ postman spec generate collection <spec-path-or-id> -n "<collection-name>"
30
+ ```
31
+
18
32
  ## Good practices for API design
19
33
 
20
34
  See [reference/rest-api-best-practices.md](reference/rest-api-best-practices.md)
@@ -31,4 +45,8 @@ Examples (in a Postman collection) are an excellent way to capture sample API re
31
45
 
32
46
  ## Workflow
33
47
  1. Establish the contract - refer to best practices. Don't just accept the user's ask - fight for the right API design.
34
- 2. Choose the instrument - Postman collection / OpenAPI spec - or both. Recommend using both to the user. Start with the Postman collection.
48
+ 2. Choose the source of truth — a Postman collection, an OpenAPI specification,
49
+ or both. Recommend both when their complementary uses benefit the task.
50
+ 3. If the specification is authoritative, generate the collection with
51
+ `postman spec generate collection` instead of manually duplicating its
52
+ operations.
@@ -1,27 +1,27 @@
1
1
  ---
2
2
  name: api-engineer
3
- description: Default entry point for API engineering work — designing, implementing, mocking, testing, monitoring, documenting, or deploying an API.
3
+ description: Makes coding agents faster and more accurate with fewer tokens when designing, implementing, mocking, testing, monitoring, documenting, or deploying an API.
4
4
  ---
5
5
 
6
6
  # API Engineer
7
7
 
8
8
  ## Foundations
9
+ Postman's skills, CLI, and Context Graph help coding agents go faster from development to production with higher accuracy and fewer tokens. Work from local files and workspace context first.
10
+
9
11
  1. Contract comes first. Establish and document the contract before starting implementation.
10
12
  2. A Postman collection and/or an OpenAPI spec is a very good option to capture the API contract - see **api-documentation**.
11
13
  3. Always validate the change against the contract you started with. Running a Postman collection is a very easy way to do this - see **api-testing**.
12
- 4. Always propose next steps. Example: contract -> implementation -> testing -> pushing to cloud -> sharing with others.
13
- 5. Don't jump straight into implementation. Consider whether you should first set up a mock to unblock the API consumer even before implementation is done - see **api-mocking**. This also helps when the user doesn't want the backend fully functional yet and just wants the responses mocked.
14
- 6. Don't push to the cloud workspace (`postman workspace push`) without user consent. The recommended way to push to the cloud is a CI step on PR merge - see **ci-integration**.
14
+ 4. Always propose next steps. Example: contract -> implementation -> testing -> updating the workspace context -> sharing with others.
15
+ 5. When the code you're changing calls other services over HTTP, use **api-simulation**: the Context Graph finds those dependencies, `postman dependency add mock <name-or-id>` pulls in each owner's mock that search finds (see **api-discovery**), and your real service runs against all of them on one port. For a stand-in backend with no real service of your own, use **api-mocking**. Mocks and simulations are persisted in your repo, so they're cheap to reuse when you resume.
16
+ 6. Don't push to the workspace (`postman workspace push`) without user consent. The recommended way to push is a CI step on PR merge - see **ci-integration**.
15
17
  7. For high-quality API search results, use **api-discovery**.
16
18
  8. No is an acceptable answer. Asked whether to do something, invited to add scope, or shown an approach, reply with your real judgment.
17
- 9. Prefer filesystem-first Postman workflows. For an existing cloud workspace,
18
- `postman workspace pull <id>` connects it and materializes its collections,
19
- environments, and specs locally. When no cloud workspace exists, `postman
20
- init --no-cloud` initializes the local structure. Work against those files,
21
- validate them, and push only with user consent; sharing an already-bound
22
- workspace means `workspace push`, not creating a duplicate. See
23
- **bootstrap** for the lifecycle decision table.
19
+ 9. Prefer filesystem-first Postman workflows. A quick overview of starting the work could be (more details in **bootstrap**):
20
+ -> if starting a new project: start by doing `postman init`. This scaffolds the relevant folders and provisions an agent-scoped session without requiring a normal Postman login.
21
+ -> if another service is only a dependency or something you want to inspect, use **api-discovery** and `postman dependency add <type> <name-or-id>` to add the relevant collection, environment, or mock. This materializes that entity under `postman/.dependencies/` and records it in `.postman/resources.yaml` without changing this repository's workspace binding.
22
+ Work against those local files and validate them. When the repository is bound to a workspace, push to persist that context for future work and share it with others.
24
23
  10. When actual use exposes a concrete Postman CLI gap or a misleading skill, handle the user's task first — then use `postman feedback` to report the gaps/bugs. Exclude secrets, user data, and proprietary content
24
+ 11. Postman secret variables let agents resolve user secrets without reading their values. See the **variables** skill.
25
25
 
26
26
  ## Dos
27
27
  1. Prove it works - validate the task against the contract. See **api-testing**.
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: api-mocking
3
- description: Stands up a fake backend that behaves like a real API — from a collection or an OpenAPI spec, running locally or pushed to Postman's cloud for a durable URL — plus request-time scenario and status-code overrides for testing failure paths. Use when the user asks to "mock this API," "create a mock server," "fake the backend," "run tests without hitting the real API," or "simulate an error/out-of-stock response." Covers `postman mock`. Depends on bootstrap for the workspace id only once a mock is pushed to the cloud (`-w`, or the workspace linked in `.postman/resources.yaml`) — generating and running a mock locally needs nothing from bootstrap.
3
+ description: Stands up a fake backend that behaves like a real API — from a collection or an OpenAPI spec, running locally or pushed to a workspace and deployed at a durable URL — plus request-time scenario and status-code overrides for testing failure paths. Use when the user asks to "mock this API," "create a mock server," "fake the backend," "run tests without hitting the real API," or "simulate an error/out-of-stock response." Covers `postman mock`. Depends on bootstrap for the workspace id only once a mock is pushed to the workspace (`-w`, or the workspace linked in `.postman/resources.yaml`) — generating and running a mock locally needs nothing from bootstrap.
4
4
  ---
5
5
 
6
6
  # API Mocking
@@ -9,25 +9,29 @@ description: Stands up a fake backend that behaves like a real API — from a co
9
9
 
10
10
  This skill covers the Postman CLI (`postman mock`) for **Code Mocks** — the
11
11
  code-based mock product. It is not the postman-app UI (Local Mode sidebar,
12
- Agent Mode tools, Simulations), nor the older classic/collection mocks that
12
+ Agent Mode tools, the Simulations surface), nor the older classic/collection mocks that
13
13
  serve saved collection examples from a `*.mock.pstmn.io` URL — that's a
14
14
  different product (the MCP `createMock` flow), not this skill.
15
15
 
16
16
  A **local** mock is two files on disk: `config.yaml` (name, port, scenarios) and
17
17
  `default.js` — a plain Node HTTP server, and the mock itself, not a wrapper
18
18
  around one. Generating, inspecting, running, and calling a local mock work for
19
- a logged-out guest. Only sharing it — pushing to the cloud and deploying a
19
+ a logged-out guest. Only sharing it — pushing to the workspace and deploying a
20
20
  durable URL — needs `postman login`. The progression is one model in three
21
- places: local folder ──`push`──▶ Code Mock (cloud definition) ──`deploy`──▶
21
+ places: local folder ──`push`──▶ Code Mock (workspace definition) ──`deploy`──▶
22
22
  Mock Server (the reachable URL).
23
23
 
24
24
  Default path: write a local folder, then `mock push` later if something other
25
25
  than you needs to hit it over the network (a teammate, CI elsewhere, a webhook
26
26
  sender). A purely local mock answering a `postman request` on your machine
27
- never needs cloud. The exception is `generate -w`, which creates the mock in a
27
+ never needs a workspace. The exception is `generate -w`, which creates the mock in a
28
28
  workspace only and writes no local files — use that only when you intentionally
29
29
  skip the repo copy.
30
30
 
31
+ Mocking another team's service, or the services your own service calls? Use
32
+ `api-simulation`, which checks each owner's workspace for a published mock
33
+ before anything is generated here.
34
+
31
35
  ## Process
32
36
 
33
37
  1. **Generate.** `postman mock generate -n NAME` with no source scaffolds a
@@ -42,7 +46,7 @@ skip the repo copy.
42
46
  the default handler from the source in place, keeping the existing name,
43
47
  port, and scenarios. `--update` still needs the `SOURCE`; it cannot be
44
48
  combined with `--output`.
45
- - `-w <workspaceId>` saves the mock to a cloud workspace *instead of* the
49
+ - `-w <workspaceId>` saves the mock to a workspace *instead of* the
46
50
  repository — it writes no local files and requires being logged in. Cannot
47
51
  be combined with `--output`, `--force`, or `--update`. Prefer local
48
52
  generate + later `push` when you still need `mock run` from a folder.
@@ -66,18 +70,18 @@ skip the repo copy.
66
70
  `mock run` it again.
67
71
  4. **Push it, if it needs to leave your machine.**
68
72
  `postman mock push ./postman/mocks/NAME` is safe to re-run — `Created` the
69
- first time, `Updated` after — and records the cloud mapping in
73
+ first time, `Updated` after — and records the workspace mapping in
70
74
  `.postman/resources.yaml`; commit that change. If a mock server is already
71
75
  live for this mock, the push updates what it serves.
72
76
  5. **Deploy it, for a URL that outlives your terminal.**
73
- `postman mock deploy CLOUD_ID -s SLUG -y` prints
77
+ `postman mock deploy WORKSPACE_MOCK_ID -s SLUG -y` prints
74
78
  `https://SLUG.mock.<team-domain>.postman.dev`. Deployed private by default —
75
79
  callers need a Postman API key (`x-api-key`) — add `--public` only when the
76
80
  mock should be reachable by anyone with the URL. `--auto-deploy` re-publishes
77
81
  the live server automatically whenever the mock changes; even without it, a
78
82
  later `push` already updates a live server, so you only re-`deploy` for the
79
83
  first URL or after taking the server down.
80
- 6. **See who's calling it.** `postman mock get CLOUD_ID` (table or `--json`)
84
+ 6. **See who's calling it.** `postman mock get WORKSPACE_MOCK_ID` (table or `--json`)
81
85
  returns `mockServerId`; feed that into `postman mock log MOCK_SERVER_ID` for
82
86
  call entries (filter with `--method` / `--status` / `--path` / `--since` /
83
87
  `--until` / `--limit`, or `--json`). An empty log means the URL genuinely
@@ -86,34 +90,58 @@ skip the repo copy.
86
90
  7. **Tear down.**
87
91
  - Local: `postman mock delete ./path --yes` — refuses while that mock is
88
92
  **running** locally; stop `mock run` first.
89
- - Cloud: `postman mock delete CLOUD_ID --yes` — refuses while the mock is
93
+ - Workspace: `postman mock delete WORKSPACE_MOCK_ID --yes` — refuses while the mock is
90
94
  **running locally** *or* **deployed**; stop the local run and take the
91
95
  mock server down first.
92
- Cloud delete doesn't touch `.postman/resources.yaml`; drop that line by hand
96
+ Workspace deletion doesn't touch `.postman/resources.yaml`; drop that line by hand
93
97
  afterward or the repo keeps claiming a mock that's gone.
94
98
 
95
99
  To point real request/assertion runs at a mock instead of hand-editing
96
100
  base-URL variables, see the `api-testing` skill's `--use-mock`/`--mock` flags
97
101
  on `collection run`.
98
102
 
103
+ To develop a service against mocks of every upstream it calls, started together
104
+ as one simulation, see the `api-simulation` skill and `postman simulation run`.
105
+
106
+ ## Stateful mocks
107
+
108
+ A mock generated from a collection or spec replays its examples, so a `POST`
109
+ and a later `GET` share nothing. To make a mock remember, keep its records in
110
+ `pm.state` inside `default.js`. `pm.state` is the mock's async key-value
111
+ store: `get`, `set`, `delete`, `has`, `keys` and `clear`. The sample from
112
+ `postman mock generate -n NAME` already uses it, and shows the pattern.
113
+
114
+ Every `mock run` or `simulation run` starts the mock with empty state; during
115
+ the run, its handler creates, reads, updates and deletes records through
116
+ `pm.state`. A deployed mock server keeps its state between requests.
117
+
118
+ - Set `interceptRequests: true` in `config.yaml` so `req.body` and `req.query`
119
+ arrive parsed.
120
+ - Seed the starting records on first use, and add a reset route that clears
121
+ and reseeds them, so a test can start clean.
122
+ - Answer an unknown id with the real service's 404, not an empty `200`.
123
+ - Every caller shares one state unless it sends `x-mock-session`. Locally that
124
+ needs `interceptRequests: true`; a deployed server reads it only when the
125
+ mock has sessions enabled.
126
+
99
127
  ## The two ids that matter
100
128
 
101
129
  - **Code-mock id** — the `id` in `config.yaml`, and the id that `push` and
102
130
  `generate -w` print (often the same value). Use it for `get`, `deploy`,
103
- `delete`, and `run` by cloud id.
104
- - **`mockServerId`** — a different value from `mock get CLOUD_ID` (table or
131
+ `delete`, and `run` by workspace mock id.
132
+ - **`mockServerId`** — a different value from `mock get WORKSPACE_MOCK_ID` (table or
105
133
  `--json`). Use it for `mock log`, and nothing else.
106
134
 
107
135
  ## Critical Rules
108
136
 
109
- 1. **When you're not signed in, every gated cloud command fails closed:**
137
+ 1. **When you're not signed in, every gated workspace command fails closed:**
110
138
  `Authentication required. Run postman login or provide --api-key`, exit 1,
111
139
  nothing half-done. (Signed in but lacking access fails differently — a
112
140
  permission or missing-workspace error.) Whether a command is gated is decided
113
141
  by what you pass it, not the verb — `mock get`/`mock run` take either a local
114
- path (ungated) or a cloud ID (gated); `mock list` is gated only when called
142
+ path (ungated) or a workspace mock ID (gated); `mock list` is gated only when called
115
143
  with no path.
116
- 2. **`push` is what moves an existing local mock to the cloud — `-w` at
144
+ 2. **`push` is what moves an existing local mock to the workspace — `-w` at
117
145
  `generate` time is optional, not a fork you must choose up front.** A mock
118
146
  built as a guest can be pushed and deployed later with no rework. Do not
119
147
  assume `generate -w` left a `postman/mocks/NAME/` folder to `run`.
@@ -132,8 +160,8 @@ on `collection run`.
132
160
  ## Verification
133
161
 
134
162
  A mock isn't done because `generate` or `run` exited 0 — hit it with
135
- `postman request` and check the actual status/body, or `mock get CLOUD_ID
136
- --json` for a cloud one, then state whether it ended up local or cloud, and
163
+ `postman request` and check the actual status/body, or `mock get WORKSPACE_MOCK_ID
164
+ --json` for a pushed one, then state whether it ended up local, pushed, or deployed, and
137
165
  (if deployed) private or public. For a scenario check, confirm a *valid* name
138
166
  from `config.yaml` actually changed the response — a typo'd name falls back to
139
167
  the default, so a 200 alone proves nothing. For a status-code check, use an
@@ -0,0 +1,186 @@
1
+ ---
2
+ name: api-simulation
3
+ description: The local development loop for a service that calls other APIs — find its dependencies in the code and the Context Graph, give each one a mock (the owning team's, or one generated from its contract), serve them together from one `.sim.yaml` on one port, run the real service against it, read the simulation's log to see what each dependency served, and ship it with the simulation in the PR. Can also inject latency, error, rate-limit or chaos per dependency. Use when the user is building or changing an endpoint that calls other services, or asks to "simulate my dependencies," "mock everything this service calls," or "run this end to end locally." Covers `postman simulation`. Builds on api-discovery for finding dependencies and their owners' mocks, and on api-mocking for the rest.
4
+ ---
5
+
6
+ # API Simulation
7
+
8
+ ## Overview
9
+
10
+ A simulation serves several mocks on one local port. Each member has a
11
+ **routeKey**, its address: with `routing: path` (the default) a dependency
12
+ lives at `http://localhost:<port>/<routeKey>`, and the routeKey is stripped
13
+ before the mock sees the request. The service under test runs for real and is
14
+ never a member.
15
+
16
+ ```yaml
17
+ # postman/simulations/feeds-dev.sim.yaml
18
+ simulation: feeds-dev
19
+ routing: path
20
+ mocks:
21
+ - routeKey: acs
22
+ path: ../mocks/acs/config.yaml
23
+ - routeKey: workspaces
24
+ path: ../.dependencies/mocks/workspace-service/config.yaml
25
+ ```
26
+
27
+ Running a local `.sim.yaml` works signed out. Running one by id, the Context
28
+ Graph, `search`, `dependency` and anything in a workspace need `postman login`.
29
+ `postman simulation` needs Postman CLI 1.70.0 or later (`postman update`);
30
+ earlier releases have only the older `postman simulate run`.
31
+
32
+ ## Commands
33
+
34
+ Most commands work either on a local `.sim.yaml` or on a simulation in a
35
+ workspace:
36
+
37
+ | Command | With a `.sim.yaml` path | With a simulation id |
38
+ | --- | --- | --- |
39
+ | `create -n <name> --mock <ref>=<routeKey>` | Writes `postman/simulations/<name>.sim.yaml` from mock paths | Creates one in the workspace from mock ids (`-w`). Don't mix the two |
40
+ | `list` | Lists `postman/simulations/` | `-w <workspaceId>` lists the workspace's |
41
+ | `get <pathOrId>` | Reads the file | Fetches it |
42
+ | `run <pathOrId>` | Serves the members on one port | Fetches the members, then serves them locally |
43
+ | `push <path>` | Uploads each member mock, then the simulation, to the linked workspace. A member pulled with `dependency add` goes up as a copy | — |
44
+ | `deploy <id>` | — | Serves it at a URL, **public by default** (rule 7). `--private` requires an API key |
45
+ | `delete <pathOrId>` | Deletes the file, not its mocks | Deletes it and takes its URL down |
46
+
47
+ ## Process
48
+
49
+ 1. **Find what the change calls.** If a `.sim.yaml` in `postman/simulations/`
50
+ already covers the change, skip to step 4. Otherwise read the code path
51
+ you're changing: its HTTP clients, their base-URL settings, and any call
52
+ the change adds. Only HTTP dependencies become members; queues, databases
53
+ and caches run as real local instances. When signed in, check that list
54
+ against the Context Graph, which also sees calls made through shared code
55
+ and other repositories:
56
+ `postman context-graph ask "What services and APIs does <repo-name> call?" --wait`.
57
+ Use the repository's name from `git remote get-url origin`, not the
58
+ folder's: a wrong name answers "no dependencies", not an error. A call the
59
+ change adds isn't in the graph yet.
60
+
61
+ 2. **Get each dependency's mock from its owner first.** This needs
62
+ `postman login`; signed out, ask the user to sign in rather than writing
63
+ mocks (rule 5). For every dependency, in this order:
64
+ 1. Find the owner's workspace:
65
+ `postman context-graph ask "Which Postman workspace holds the collections and mocks for <service>?" --wait`.
66
+ If the workspace it names has neither, find the owner's with
67
+ `postman search workspaces "<service>"`.
68
+ 2. List that workspace's mocks:
69
+ `postman search mocks --filter "workspaceId=<id>"`. A name match in
70
+ another workspace is not ownership.
71
+ 3. If the owner has one, pull it with `postman dependency add mock <mockId>`
72
+ and use the copy as it is, even if the owner also deployed it.
73
+ 4. Only if the owner has none, make one: pull their collection with
74
+ `postman dependency add collection <id>` and run `postman mock generate`
75
+ on it (see `api-mocking`). Keep the endpoints this path calls and answer
76
+ `501` for the rest, so a missed call fails loudly. Make it stateful when
77
+ the change writes something and reads it back.
78
+
79
+ 3. **Compose the simulation.**
80
+
81
+ ```bash
82
+ postman simulation create -n feeds-dev \
83
+ --mock ./postman/mocks/acs=acs \
84
+ --mock ./postman/.dependencies/mocks/workspace-service=workspaces
85
+ ```
86
+
87
+ Each `=<routeKey>` names a member: lowercase letters, digits, `-` and `_`.
88
+ Schema: [reference/sim_yaml_schema.md](reference/sim_yaml_schema.md).
89
+
90
+ 4. **Run it and point the service at it.**
91
+
92
+ ```bash
93
+ postman simulation run postman/simulations/feeds-dev.sim.yaml --port 4900 --output ndjson > sim.ndjson 2> sim.err &
94
+ ```
95
+
96
+ Keep stderr out of `sim.ndjson` so every line parses; start-up errors land
97
+ in `sim.err`. It's up when the `listening` event lists each member's
98
+ address. Set each upstream base URL to `http://localhost:4900/<routeKey>`,
99
+ never to a mock server's URL.
100
+
101
+ Stop it with
102
+ `pkill -INT -f "simulation run postman/simulations/feeds-dev.sim.yaml"`:
103
+ every member stops, and the closing `summary` event counts the requests
104
+ served and failed. `kill $!` reaches only the `postman` launcher, which
105
+ ignores it, so the simulation keeps serving. In a terminal of its own,
106
+ Ctrl+C works too.
107
+
108
+ 5. **Develop and test, reading the simulation log after each call.** Call
109
+ the changed route on the real service with `postman request` or its
110
+ collection (see `api-testing`). Every request a member served is a
111
+ `request` event in `sim.ndjson`, with its `routeKey`, `method`, `path`,
112
+ `statusCode`, `duration` in milliseconds, and both bodies. Check that each
113
+ upstream call you expect is there, on the right member, with the status you
114
+ expect. Restart the service after a code change, and the simulation after a
115
+ mock edit. A response from the simulation itself means wiring, not code:
116
+ - `404 Unknown simulation member`: the path has no valid routeKey. The body
117
+ lists the valid ones. It isn't logged.
118
+ - `501`: an upstream call you haven't modelled yet. Add it to your own
119
+ mock. For a pulled one, fork it into `postman/mocks/` first.
120
+ - `502 Proxy error`: the member's handler crashed. See `sim.ndjson`.
121
+
122
+ 6. **Optionally, fail a dependency.** Worth doing when the change has to
123
+ survive an upstream that errors or is slow, but not required. Copy the
124
+ baseline `.sim.yaml`, give that member a condition, run the service
125
+ against it, and report what it did.
126
+
127
+ ```yaml
128
+ - routeKey: workspaces
129
+ path: ../.dependencies/mocks/workspace-service/config.yaml
130
+ scenarios:
131
+ - overrides:
132
+ conditions:
133
+ error: { status_code: 503 }
134
+ ```
135
+
136
+ The conditions are `latency`, `error`, `rate_limit` and `chaos`; the schema
137
+ has all four. Each applies to every route on that member, and the first to
138
+ fail a request ends it, so fail a dependency one way at a time. An injected
139
+ error, `429` or chaos failure never reaches the mock and isn't logged, so
140
+ judge it by what the service returned.
141
+
142
+ 7. **Ship.** Commit your mocks, the `.sim.yaml` files and
143
+ `.postman/resources.yaml`, but not `sim.ndjson`: it records request
144
+ headers and bodies, tokens included. After a clone,
145
+ `postman dependency install` restores the pulled mocks. Running the
146
+ simulation in CI is optional: start it as in step 4 before the test step,
147
+ and stop it after.
148
+
149
+ ## Critical Rules
150
+
151
+ 1. **The service under test is never a member.** If nothing real runs, use
152
+ `api-mocking`.
153
+ 2. **Unique routeKeys and an explicit `--port`.** A member's own `port:` is
154
+ ignored. Without `--port`, a busy 3000 silently moves to a random port.
155
+ 3. **Members share one `pm.state` store.** Two stateful mocks that use the
156
+ same key read and overwrite each other's records, so give each its own keys.
157
+ 4. **Only `overrides.conditions` inject faults.** A bare
158
+ `scenarios: - path: …/default.js` entry, as the Postman app writes, adds
159
+ none. `overrides.bypass` is ignored.
160
+ 5. **Never write or generate a mock for a dependency before searching its
161
+ owner's workspace.** A mock you write encodes your assumptions; the
162
+ owner's encodes their contract and their state.
163
+ 6. **Pull an owner's mock even when it's deployed.** Only members take faults
164
+ and log requests, and a private mock server needs an `x-api-key` your
165
+ service won't send. Never edit `postman/.dependencies/`:
166
+ `dependency update` overwrites it. To change a pulled mock, copy it into
167
+ `postman/mocks/` and call it a fork.
168
+ 7. **Confirm before a public deploy.** `simulation deploy` without
169
+ `--private`, or with `-y`, serves every member's mock to anyone with the
170
+ URL. Ask the user first; a local `run` needs no deploy at all.
171
+
172
+ ## Verification
173
+
174
+ Starting is not the same as being used. Before you report, read the whole
175
+ log: every member on the path should have `request` events with the statuses
176
+ you expect. A member with none isn't wired or isn't on this path.
177
+
178
+ Report:
179
+
180
+ - **How the simulation performed:** for each member, the requests it served
181
+ and their statuses, and anything unexpected, such as a `501`, a `502` or a
182
+ member that was never called.
183
+ - **Where each mock came from:** for each dependency, the owner workspace you
184
+ searched, what you found there, and whether its mock was pulled, forked or
185
+ generated here.
186
+ - **What ran for real.**