API guide verification ledger
Scope: every > Not confirmed by code: block in user-guide/api/00-README.md to 08-workflows-by-example.md.
Method: static code reading only. No build, no server, no database.
Outcomes: CONFIRMED (now plain fact), CORRECTED (docs were wrong or incomplete, text fixed), UNRESOLVABLE (block kept, rewritten with exact steps to verify).
Paths are under crates/ unless noted. 00-README.md:5 is the legend sentence, not a block, and is unchanged.
| file | original claim | outcome | evidence | action taken |
|---|---|---|---|---|
| 01-quickstart.md | Responses are built from code, not a live server | UNRESOLVABLE | Needs a running server | Block kept, added steps to verify with curl -i |
| 02-authentication.md | User status (Locked/Disabled) does not stop login | CONFIRMED | libs/wms-lib-core/src/model/user.rs:26-33 ("Descriptive only"); no status read in libs/wms-lib-web/src/handlers/handlers_login.rs; API key check reads only api_access, expiry, revoke (libs/wms-lib-auth/src/authentication/api_key.rs:183,247) | Block replaced by fact; added that API keys of Locked/Disabled users also work Superseded: fixed by F-03 (5cb291af1); the page now says Locked and Disabled are refused. |
| 02-authentication.md | Default of api_access for new users unknown | CONFIRMED (default true) | libs/wms-lib-sql/sql/wms-enterprise/migrations/001_baseline.sql:224 (DEFAULT true NOT NULL); libs/wms-lib-core/src/model/user.rs:142 (Option<bool> on create) | Block replaced by fact |
| 02-authentication.md | Which methods work in enterprise scope | CORRECTED (answered) | libs/wms-lib-web/src/middleware/mw_auth.rs:338-349; libs/wms-lib-core/src/model/store/dbx/mod.rs:240-247,268 (search_path = enterprise only for enterprise scope, <wh>, enterprise for warehouse) | Rule written: only enterprise-table methods work in enterprise scope |
| 02-authentication.md | How tenants map to base URLs | CONFIRMED for Kubernetes provisioner only | services/tenant-manager/src/provision_k8s.rs:172-173; k8s/base/app-server/templates/ingress.yaml (host {{tenant_name}}.{{base_domain}}) | Block replaced by fact, with "other deployments: ask admin" |
| 03-request-and-response-format.md | Error for a batch array | CONFIRMED | services/app-server/src/web/routes_rpc.rs:183-251 (body is Value; Request::try_from); rpc-router-0.1.3 request/request.rs:17-21 (VersionMissing); libs/wms-lib-web/src/error.rs:346-349 (400, RPC_REQUEST_INVALID) | Block replaced by fact |
| 03-request-and-response-format.md | How several objects in filters combine | CONFIRMED (OR of AND groups) | modql-0.4.1 src/filter/nodes/group.rs:117-150 (Condition::any over groups, Condition::all inside); libs/wms-lib-rpc-core/src/rpc_params.rs:327-332 (Option<OneOrMany<_>>); libs/wms-lib-core/src/model/base/crud_fns.rs:223 | Block replaced by fact |
| 04-command-types-and-concurrency.md | Undeclared writes may have hidden checks | CONFIRMED (general fact) | Idempotency is per handler, not global: libs/wms-shared/src/idempotency.rs, pipeline_runner.rs; generic update has no expected-version check (crud_fns.rs:385-425 is the versioned variant only) | Block replaced by a plain sentence: "no version check" is not "no rule" |
| 04-command-types-and-concurrency.md | Exact current_state fields per command | CORRECTED | libs/wms-outbound/src/repo/occ.rs:78-109 (order, line); libs/wms-outbound/src/waves/error.rs:111-115 (whole Wave/WavePolicy); libs/wms-outbound/src/packing/commands/carton_close.rs:166; libs/wms-outbound/src/picking/commands/dissolve.rs:797-815; libs/wms-inventory/src/service/inventory_count/mod.rs:182,200; inventory_adjustment/mod.rs:361; inventory_count_policy/save.rs:48; libs/wms-lib-core/src/model/policies/putaway_policy.rs:282 and base/crud_fns.rs:437 (version only); libs/wms-inventory/src/putaway/pipeline/save.rs:244 (conflicts[], no version); libs/wms-tasks/src/error.rs:367-370 (whole task) | Table rewritten per domain; block deleted |
| 05-errors.md | Meaning text for serial codes 88-93 | CORRECTED | Enum libs/wms-shared/src/validation_codes.rs:123-128. 88: libs/wms-serial/src/service/serial/commands/return_serial.rs:70-82 (is_overridable: true). 91: libs/wms-serial/src/service/serial_service.rs:257-273 (is_overridable: true). 92: serial_service.rs:284-297 (false), check at 632-655. 89 and 90: check_two_locations (529) and check_velocity (560) have no caller. 89 and 93: only log warnings in services/app-server/src/services/cleanup_tasks.rs:1097,1176. Override capability is None for all: validation_codes.rs:595-610. "Owner policy" does not exist in code | Rows 88-93 rewritten; false "by owner policy" claim removed; block deleted |
| 05-errors.md | (found while checking) how serial codes reach the client | CORRECTED | services/app-server/src/web/rpcs/serial_commands_rpc.rs:37-39 (map_serial_err -> ModelError::Validation(string)); libs/wms-lib-web/src/error.rs:297-300 (HTTP 400 INVALID_DATA); receipt path keeps the code: libs/wms-inbound/src/service/receipt/receive/serial_registration.rs:72 | Note added under the code table |
| 05-errors.md | Lists of context.code strings complete? | CORRECTED (not complete) | More fn code() / consts in libs/wms-outbound/src/domain/pick_assignment/{aggregate,route,delivery,tare}.rs, picking/infrastructure/holder_change.rs:75-81, cartonization/repair.rs:43-50. Putaway adds none (libs/wms-inventory/src/putaway has no "code": literals) | Block replaced by "not complete" note naming the extra sources |
| 05-errors.md | Status/text for malformed login body | CONFIRMED | libs/wms-lib-web/src/handlers/handlers_login.rs:157 (Json<LoginPayload>); axum-0.8.9 extract/rejection.rs (400 syntax, 415 content-type, 422 data) | Block replaced by 3-row table and "plain text body" |
| 06-rate-limits-and-limits.md | Limits added by proxy/gateway | UNRESOLVABLE | k8s/ has no rate-limit or body-size middleware, but live deployment is unknown | Block kept, narrowed, added a curl loop to verify |
| 06-rate-limits-and-limits.md | Max body size | CONFIRMED | No DefaultBodyLimit/RequestBodyLimit anywhere in crates; axum-core-0.5.6 ext_traits/request.rs:319 (2,097,152 bytes), rejection PAYLOAD_TOO_LARGE | Block replaced by fact: 2 MB, HTTP 413 |
| 06-rate-limits-and-limits.md | Business rules limiting command input | CORRECTED (they exist) | libs/wms-outbound/src/waves/policy.rs:44-48,401-410 (max_orders 1..1000, max_lines 1..100000, max_waves_per_run 1..20); wave_rpc.rs:178-186 | Block replaced by fact |
| 07-webhooks-and-events.md | Is every code in the table published? | CORRECTED | 11 seed codes have no Rust literal (listed in doc). process.order.picked was in the doc but only appears in tests: services/web-ui-server/src/live/outbox_db_tests.rs:133,219 | Seed-only list added; .picked removed from Order row |
| 07-webhooks-and-events.md | Any API/screen edits event_filter, warehouse_filter, webhook_headers | CONFIRMED (none) | libs/wms-lib-core/src/model/integration/integration_subscriber.rs:42-61 (no such fields); no match in wms-meta-ui SQL, web-ui-server, ui/src | Block replaced by fact; seed subscriber ipaas described (wms-enterprise/migrations/001_baseline.sql:1404) |
| 07-webhooks-and-events.md | HTTP client time-out and redirects | CORRECTED | services/app-server/src/services/webhook_security.rs (30 s production timeout, Policy::none(), validated and pinned DNS answers) | Redirect and destination-security behavior updated after B1-03 |
| 07-webhooks-and-events.md | Stable event id in the webhook body | CONFIRMED (none) | services/app-server/src/services/integration_delivery.rs:597-604 (body fields: event_name, entity_name, key1, warehouse, tenant_id, payload) | Block replaced by fact. Also fixed the false mention of key2 (not in body) |
| 07-webhooks-and-events.md | API for a poll subscriber to mark read | CONFIRMED (none) | Reference has only list/get/count for deliveries; integration_delivery.rs:255-257 (poll: debug log only); route string appears only in a SQL comment (archive_pre_baseline/005_integration_event_system.sql:206) | Block replaced by fact; added note that poll rows do not hold back event_status (integration_delivery.rs:278,299-304) |
| 07-webhooks-and-events.md | Wiring subscriber to integration-platform flow | CORRECTED | services/integration-platform/src/handlers/event_handler.rs:10-24 and server.rs:141,213-215 (POST /api/v1/events/emit, X-Api-Key, tenant from key); seed URL in baseline.sql:1404. Doc said POST /events/emit (wrong path) | Path fixed; wiring explained. Which flow runs is platform data, not WMS code (stated) |
| 08-workflows-by-example.md | Responses built from types, not live run | UNRESOLVABLE | Needs a running server | Block kept, added steps to verify |
| 08-workflows-by-example.md | Does putaway run as a Putaway task? | CONFIRMED (no) | Only TaskDraft creators are picking (libs/wms-outbound/src/picking/infrastructure/tasks.rs) and counting (libs/wms-inventory/src/domain/inventory_count/postgres_repository.rs:573); no create-task RPC in reference/tasks.md | Block replaced by fact |
| 08-workflows-by-example.md | Steps between closed pallet and shipping | CORRECTED | No dispatch method in any reference group. confirm_shipment and create_picking_task are stubs: services/app-server/src/web/rpcs/wms_task_rpc.rs:10-33. process.shipment.dispatched is never published (only seed, baseline.sql:1057) | Block replaced by fact and a "do not use the stubs" warning. Update: both stubs were removed (F-07, 48086a858); the warning is gone from the page. |
Counts
- Blocks handled: 24 (00-README.md:5 is a legend, not a block).
- CONFIRMED: 13. Of these, 12 were rewritten as plain fact; the tenant URL fact holds for the Kubernetes provisioner only.
- CORRECTED: 8 (enterprise scope,
current_statefields, serial codes 88-93,context.codelists, business limits, event codes, integration-platform wiring, post-pallet shipping). - UNRESOLVABLE (block kept, with steps to verify): 3 (01 quickstart sample responses, 08 sample responses, proxy/gateway limits).
- Also fixed outside any block:
key2is not in the webhook body; the platform path is/api/v1/events/emit;process.order.pickedremoved from the event table.
Product findings
- Stub RPCs that look real.
confirm_shipmentandcreate_picking_task(services/app-server/src/web/rpcs/wms_task_rpc.rs:10-33) log "STUB" and return fixed fake JSON. They are in the public catalog with roles ADMIN and MANAGER. A client can believe a shipment was confirmed. Remove them, or mark them clearly. Fixed (F-07, commit48086a858): both methods were removed from the catalog. - Serial commands drop the numeric error code.
map_serial_err(services/app-server/src/web/rpcs/serial_commands_rpc.rs:37-39) turns aSerialError::ValidationFailedinto a textINVALID_DATA. Thereturn_code(80-95),classandis_overridableare lost. The receipt path keeps them (wms-inbound/.../serial_registration.rs:72). The same code means different wire shapes in two paths. Fixed (F-14): theserial_*methods answer the RF envelope (SerialError::into_model_error) with the code, class and override flag. - Overridable flag with no way to override. Codes 88 and 91 set
is_overridable: true(return_serial.rs:75-82,serial_service.rs:257-273), but no serial code has an override capability (wms-shared/src/validation_codes.rs:595-610) andserial_returntakes no acknowledgement. The "owner policy" described in the enum comment (validation_codes.rs:472) is not implemented. - Dead anomaly rules.
check_two_locations(89) andcheck_velocity(90) inlibs/wms-serial/src/service/serial_service.rs:529,560have no caller. Code 93 and the 89 scan only write log warnings (services/app-server/src/services/cleanup_tasks.rs:1097,1176). The eventprocess.serial.flagged_anomalyhas a payload type but nothing in the anomaly path publishes it. Fixed (P-22):serial_shiprunscheck_velocityand publishesprocess.serial.flagged_anomaly;check_two_locationsandcheck_lot_mismatchwere removed (89 stays a background log scan). - Locked/Disabled users still authenticate.
UserStatusis not checked at login or in API key validation (libs/wms-lib-core/src/model/user.rs:26-33,api_key.rs:247). Onlyapi_accessblocks keys. A departed employee markedDisabledkeeps access. The code comment already flags it as a missing gate. api_accessflip is delayed up to 30 s (api_key.rs:248-258): no cache invalidation on that change. Revoke does invalidate. Already documented in 02.- Event prefix filter is loose.
prefix.*is matched asstarts_with(prefix)without the dot (services/app-server/src/services/integration_delivery.rs:440-442).process.*also matches a code likeprocessfoo. A pattern such asprocess.order.*matchesprocess.order_x...too. Low risk today, but not the documented meaning. Fixed (F-17):prefix.*takes only the codes under the prefix, at a.(event_code_matches). - Resolved B1-03: guarded
webhook_url. Create/update still store text, but the worker accepts only HTTP(S), refuses redirects, checks every resolved address, and pins the connection to the validated set. Exact tenant/system-scoped private exceptions requireDELIVERY_WEBHOOK_PRIVATE_ALLOWLIST. - Axum rejections bypass the JSON-RPC envelope.
rpc_handlertakesJson(rpc_body)(routes_rpc.rs:187). Invalid JSON, wrong Content-Type or a body over 2 MB gets a plain-text Axum answer (400/415/413/422), not a JSON-RPC error. Clients must handle both shapes. Fixed (F-16):rpc_handlertakes the body asResult<Json<Value>, JsonRejection>and answersRPC_REQUEST_INVALIDin the envelope, keeping the HTTP status (wms_lib_web::Error::RpcBodyRejected)./api/loginstill answers plain text. - Orphan event codes. Eleven seeded codes in
event_registryare never published (list in 07). A subscriber that filters on them waits for nothing. Fixed (F-18):is_activenow says what is published (enterprise 019:adjusted/movedon,reversaloff);wms-lib-core --test event_catalog_censuskeeps the catalog and the code in step. - Generated reference is silent on stubs.
reference/tasks.mdlistsconfirm_shipmentwithout any hint that it is a stub. The generator (scripts/docs-api/generate_reference.py) has no stub marker. Not changed: it needs a source-level marker first.