procurement-ingest/lambdas/api/docs.html

163 lines
6.5 KiB
HTML
Raw Normal View History

feat(api): procurement-api read stack + OpenAPI docs (SHOC reconciliation path) (#127) * feat(api): add procurement-api stack - read API + OpenAPI docs page Third CDK stack: API Gateway REST API (IAM SigV4) over both pipelines' tables, replacing SHOC's retired SyncController cross-account DynamoDB scan as the reconciliation/backfill path. - lambdas/api/: handler (healthcheck + docs-token gate + router dispatch), router (single route table), pagination (opaque cursor, hostile -> 400), Decimal-safe serialization, wo_repo/po_repo reads. No VendorReplies. - OpenAPI 3.1 spec as source of truth incl. top-level webhooks section documenting the outbound SHOC feed; phase-2 write endpoints x-planned (router answers 501). Self-contained /docs page, no CDN. - Auth: AWS_IAM on data routes + resource policy scoped to exactly arn:aws:iam::396287094661:role/shoc-backend-dev on GET/*; /docs and /openapi.json carve-out is token-gated in the Lambda via shared web_ui_auth (fail-closed, INFRA-74 posture). - KMS: explicit Decrypt/DescribeKey on the DynamoDB CMK from SSM (name-imported table drops the key association - INFRA-104 class). - Alarms: errors/throttles/duration(p99>=22.5s) + gateway 5xx, ALARM-only to site-alerts. No access logging in v1 (docs ?token= shim stays out of logs); cloud_watch_role=False. - Tests: handler auth-seam + routing + Decimal round-trip; moto cursor pagination incl. hostile cursors; spec<->router drift gate; bundle AST pins for the api command; pytest.ini --cov + loader siblings. - Deploy role: third stack DescribeStacks ARN + procurement-api smoke invoke ARN (re-run create-deploy-role.sh before merge). * harden(api): apply sh-security-review findings to procurement-api Fan-out (6 detectors) + review findings resolved: Correctness / DoS: - pagination: require EXACT key-set match (was subset) so a partial/foreign composite cursor can't reach DynamoDB as an inconsistent ExclusiveStartKey -> ValidationException -> 500; comments Query now pins the cursor's work_order_id to the path entity. - handler: map botocore ValidationException to 400 (defense in depth) so a crafted cursor can't drive the zero-threshold 5xx alarm. - web_ui_auth: compare tokens as bytes; a non-ASCII presented token now fails closed (401) instead of crashing hmac.compare_digest into a 500. Resolves the pre-existing xfail(strict) follow-up test; hardens the web UIs too. Docs page: - typeStr() now escapes the one spec-derived string that reached innerHTML. - spec inlined into the docs <script> block escapes "<" -> < (</script> breakout guard); /openapi.json still served byte-faithful. - Cache-Control: no-store + Referrer-Policy: no-referrer on docs responses so the ?token= URL stays out of caches/Referer. - spec-drift test asserts the committed spec carries no "</" / "<!--". IAM / IaC: - resource policy enumerates the 7 data GET resources instead of GET/* so a future GET route can't silently inherit SHOC cross-account reach. - kms:Decrypt grant gains a kms:ViaService=dynamodb condition. - stage throttling (50 rps / 100 burst) bounds the unauthenticated /docs blast radius below the 10k account default. - corrected the PATCH/POST comment (same-account callers aren't blocked by the resource policy; 501 handler + absent write grant are the gate). - documented the RETAIN log-group first-deploy rollback trap and the resource-policy-needs-redeploy gotcha in-stack. Mandatory GPT-4.1 cross-family review of the full policy surface: no BLOCK/FIX. 675 tests pass, ruff clean, cdk synth green.
2026-07-23 19:32:20 -04:00
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Procurement Ingest API</title>
feat(api): Redocly lint gate + SHOC-themed /docs (Redoc theming, topbar, collapsible samples) (#130) * feat(api): Add @redocly/cli as a dev dependency Signed-off-by: Adam Moussa <adam@seahavenind.com> * feat(api): Add Redocly configuration file with custom rules Signed-off-by: Adam Moussa <adam@seahavenind.com> * chore(api): Redocly lint config + bring openapi.json into compliance redocly.yaml from the Redocly guidelines builder, with three generated rules corrected: response-contains-property had the status codes as the required body fields (intent was the Error schema's top-level 'error'; 403 exempt since API Gateway emits AWS's {message} shape, 501 not 503); operation-4xx-problem-details-rfc7807 off (adopting RFC 7807 would be a runtime + SHOC-contract change, decided against); the two inert casing rules (parameter names, schema properties) removed because both name sets are contract-pinned (gateway resource paths, DynamoDB items). Spec changes, no runtime impact: operationIds renamed to method-prefixed kebab-case (get-work-orders, post-work-order-comment, ...); tags added to all 15 operations + root tags object (groups the Redoc sidebar); examples on all six parameters; license field; server description punctuation; two descriptions reworded to start capitalized. Real linter catches fixed: the two x-planned ops were missing their {workOrderId} path parameter and any 4xx response (403 added - true today, gateway rejects unsigned). .redocly.lint-ignore.yaml pins the six deliberate exceptions: webhook keys are the shipped SHOC contract event names (not renameable), and the x-planned ops answer only 501 (no 2xx to document). package.json: npm run lint:api. Verified: lint 0 errors, 675 pytest, headless-Chrome render of the tagged docs page. * feat(api): SHOC design-system theme for /docs (vendored fonts) Themes the Redoc page with the canonical SHOC token set: Montserrat 600 headings / DM Sans body / JetBrains Mono code, primary #1c75bc, navy #262262 sidebar text + right panel, #f9fafb background, 244px sidebar. sortRequiredPropsFirst on; 200 responses pre-expanded. Fonts ship as lambdas/api/fonts.css (latin woff2 subsets from @fontsource 5.3.0, embedded as data URIs, ~90KB) and inline via a new __FONTS_CSS__ placeholder with the same </style breakout guard -- the offline single-response invariant holds, nothing fetches Google Fonts (test-pinned). Bundling cp + bundle-consistency pin + spec-drift asset checks extended. Verified: headless-Chrome render (theme + fonts applied), ruff, 675 pytest, cdk synth + staged-asset check. * feat(api): SHOC gradient topbar on /docs 64px fixed header with the SHOC shell gradient token (#1b1f52 -> #1c4f8f -> #1c75bc), Sea Haven wordmark in Montserrat 600, page name right-aligned in DM Sans. Redoc's scrollYOffset: 64 keeps the sticky sidebar and anchor scrolling clear of the fixed bar. Verified via headless-Chrome render. * style(api): normalize /docs header and right-panel blues The right panel's #262262 is a purple-leaning navy that clashed with the cyan-leaning gradient, and the bar's brightest point sat directly over the dark panel. Right panel now uses #1b1f52 (the gradient's own dark endpoint) and the gradient runs bright-to-dark so its dark end lands flush on the panel -- no seam, one blue family. Verified via headless-Chrome render. * style(api): right-panel gradient on /docs via bundle-pinned override Redoc's theme only takes solid colors (it derives shades from rightPanel.backgroundColor), so the gradient (#1b3d79 -> #1b3068 -> #1b1f52, continuing the topbar blend) rides as a CSS override on the styled-components classes of the per-section right-panel divs (.sc-iGgWBj.sc-gsFSXq + the .sc-dExYaf stub). Those names are deterministic for the vendored 2.5.3 bundle (verified across loads) but change on any Redoc bump: re-derive via headless probe (find elements whose computed background equals the rightPanel color). If they stop matching, the panel falls back to the solid #1b1f52 theme color -- cosmetic only. Verified via headless-Chrome render. * feat(api): collapsible samples column on /docs Redoc CE has no built-in panel toggle, so the topbar gains a Hide/Show samples button that flips .samples-collapsed on <html>: the right-panel divs hide (same bundle-pinned styled-components classes as the gradient override) and each section's content half takes the full width. Choice persists in localStorage; aria-pressed tracks state. If the pinned classes stop matching after a Redoc bump the toggle goes inert -- cosmetic only. Both states verified via headless-Chrome render. * ci(api): spec-lint CI gate + npm Dependabot coverage New spec-lint job mirrors the local npm run lint:api so openapi.json cannot drift from redocly.yaml with green CI. Dependabot gains the npm ecosystem (package.json is new; nothing watched @redocly/cli). * feat(api): docs finishing touches - x-tagGroups, favicon, docs:preview x-tagGroups sections the Redoc sidebar (Read API / Meta / SHOC Feed); inline data-URI SVG favicon (SHOC blue) stops the browser's follow-up /favicon.ico request 403ing at the gateway; npm run docs:preview wraps the real-handler local render (scripts/preview_docs.py); README gains a docs-page architecture section covering the inline pattern, theme, pinned-selector caveat, and tooling. Lint 0 errors, 675 pytest, headless render verified. --------- Signed-off-by: Adam Moussa <adam@seahavenind.com>
2026-07-24 14:04:30 -04:00
<link rel="icon" href="data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%2016%2016'%3E%3Crect%20width='16'%20height='16'%20rx='3'%20fill='%231c75bc'/%3E%3Ctext%20x='8'%20y='12'%20font-size='10'%20font-family='sans-serif'%20font-weight='700'%20text-anchor='middle'%20fill='white'%3ES%3C/text%3E%3C/svg%3E">
feat(api): stock Swagger UI for /docs (vendored offline) (#128) * feat(api): use stock Swagger UI for the /docs page Replaces the custom renderer with vendored stock Swagger UI (swagger-ui-dist 5.17.14, Apache-2.0), kept offline (no CDN) and inlined server-side into the single token-gated /docs response alongside the spec. BaseLayout (topbar hidden); try-it-out disabled since data routes need SigV4 (use Postman for live calls). Breakout guards on the inlined css/js/spec. Bundle-consistency + spec-drift + handler tests updated for the two vendored assets. cdk diff = Lambda code asset only (no IAM/API/policy change). * fix(api): render Swagger UI with the canonical StandaloneLayout recipe The BaseLayout-only init (apis preset, no standalone preset) rendered incorrectly. Switch to the canonical swagger-ui-dist recipe: vendor swagger-ui-standalone-preset.js and init with presets:[apis, SwaggerUIStandalonePreset] + layout:"StandaloneLayout" (topbar hidden, try-it-out disabled). Verified via headless Chrome against the live deployed page: all 9 endpoints + 4 webhooks + models render. Tests/bundling updated for the third vendored asset. * fix(api): declutter the Swagger UI docs page The page rendered (stock Swagger UI, StandaloneLayout) but looked cramped: a dense info.description wall of inline-code chips collided across Swagger UI's tight default line-height, and the Servers box showed a SEE-STACK-OUTPUT placeholder. - Trim info.description to a few concise lines (detail lives in README + the webhook contract doc). - Inject the live invoke URL into servers[0].url per request (from the API Gateway request context; committed literal is the fallback), so the Servers box shows the real endpoint and drops the server-variables section. - CSS: loosen description line-height + inline-code padding so chips never overlap; tidy the scheme container spacing. - Handler: cache the heavy CSS/JS/preset shell once; splice the (server- injected) spec per request. Verified via headless Chrome against the live deployed page.
2026-07-23 20:19:47 -04:00
<!--
Redoc (redoc.standalone.js, MIT), vendored offline and inlined server-side
so /docs is a single token-gated request with no CDN or follow-up asset
feat(api): Redocly lint gate + SHOC-themed /docs (Redoc theming, topbar, collapsible samples) (#130) * feat(api): Add @redocly/cli as a dev dependency Signed-off-by: Adam Moussa <adam@seahavenind.com> * feat(api): Add Redocly configuration file with custom rules Signed-off-by: Adam Moussa <adam@seahavenind.com> * chore(api): Redocly lint config + bring openapi.json into compliance redocly.yaml from the Redocly guidelines builder, with three generated rules corrected: response-contains-property had the status codes as the required body fields (intent was the Error schema's top-level 'error'; 403 exempt since API Gateway emits AWS's {message} shape, 501 not 503); operation-4xx-problem-details-rfc7807 off (adopting RFC 7807 would be a runtime + SHOC-contract change, decided against); the two inert casing rules (parameter names, schema properties) removed because both name sets are contract-pinned (gateway resource paths, DynamoDB items). Spec changes, no runtime impact: operationIds renamed to method-prefixed kebab-case (get-work-orders, post-work-order-comment, ...); tags added to all 15 operations + root tags object (groups the Redoc sidebar); examples on all six parameters; license field; server description punctuation; two descriptions reworded to start capitalized. Real linter catches fixed: the two x-planned ops were missing their {workOrderId} path parameter and any 4xx response (403 added - true today, gateway rejects unsigned). .redocly.lint-ignore.yaml pins the six deliberate exceptions: webhook keys are the shipped SHOC contract event names (not renameable), and the x-planned ops answer only 501 (no 2xx to document). package.json: npm run lint:api. Verified: lint 0 errors, 675 pytest, headless-Chrome render of the tagged docs page. * feat(api): SHOC design-system theme for /docs (vendored fonts) Themes the Redoc page with the canonical SHOC token set: Montserrat 600 headings / DM Sans body / JetBrains Mono code, primary #1c75bc, navy #262262 sidebar text + right panel, #f9fafb background, 244px sidebar. sortRequiredPropsFirst on; 200 responses pre-expanded. Fonts ship as lambdas/api/fonts.css (latin woff2 subsets from @fontsource 5.3.0, embedded as data URIs, ~90KB) and inline via a new __FONTS_CSS__ placeholder with the same </style breakout guard -- the offline single-response invariant holds, nothing fetches Google Fonts (test-pinned). Bundling cp + bundle-consistency pin + spec-drift asset checks extended. Verified: headless-Chrome render (theme + fonts applied), ruff, 675 pytest, cdk synth + staged-asset check. * feat(api): SHOC gradient topbar on /docs 64px fixed header with the SHOC shell gradient token (#1b1f52 -> #1c4f8f -> #1c75bc), Sea Haven wordmark in Montserrat 600, page name right-aligned in DM Sans. Redoc's scrollYOffset: 64 keeps the sticky sidebar and anchor scrolling clear of the fixed bar. Verified via headless-Chrome render. * style(api): normalize /docs header and right-panel blues The right panel's #262262 is a purple-leaning navy that clashed with the cyan-leaning gradient, and the bar's brightest point sat directly over the dark panel. Right panel now uses #1b1f52 (the gradient's own dark endpoint) and the gradient runs bright-to-dark so its dark end lands flush on the panel -- no seam, one blue family. Verified via headless-Chrome render. * style(api): right-panel gradient on /docs via bundle-pinned override Redoc's theme only takes solid colors (it derives shades from rightPanel.backgroundColor), so the gradient (#1b3d79 -> #1b3068 -> #1b1f52, continuing the topbar blend) rides as a CSS override on the styled-components classes of the per-section right-panel divs (.sc-iGgWBj.sc-gsFSXq + the .sc-dExYaf stub). Those names are deterministic for the vendored 2.5.3 bundle (verified across loads) but change on any Redoc bump: re-derive via headless probe (find elements whose computed background equals the rightPanel color). If they stop matching, the panel falls back to the solid #1b1f52 theme color -- cosmetic only. Verified via headless-Chrome render. * feat(api): collapsible samples column on /docs Redoc CE has no built-in panel toggle, so the topbar gains a Hide/Show samples button that flips .samples-collapsed on <html>: the right-panel divs hide (same bundle-pinned styled-components classes as the gradient override) and each section's content half takes the full width. Choice persists in localStorage; aria-pressed tracks state. If the pinned classes stop matching after a Redoc bump the toggle goes inert -- cosmetic only. Both states verified via headless-Chrome render. * ci(api): spec-lint CI gate + npm Dependabot coverage New spec-lint job mirrors the local npm run lint:api so openapi.json cannot drift from redocly.yaml with green CI. Dependabot gains the npm ecosystem (package.json is new; nothing watched @redocly/cli). * feat(api): docs finishing touches - x-tagGroups, favicon, docs:preview x-tagGroups sections the Redoc sidebar (Read API / Meta / SHOC Feed); inline data-URI SVG favicon (SHOC blue) stops the browser's follow-up /favicon.ico request 403ing at the gateway; npm run docs:preview wraps the real-handler local render (scripts/preview_docs.py); README gains a docs-page architecture section covering the inline pattern, theme, pinned-selector caveat, and tooling. Lint 0 errors, 675 pytest, headless render verified. --------- Signed-off-by: Adam Moussa <adam@seahavenind.com>
2026-07-24 14:04:30 -04:00
fetch. The bundle, the SHOC design-system fonts (fonts.css, data-URI
@font-face), and the OpenAPI spec are substituted for the placeholders
below by lambdas/api/handler.py::_docs_shell / _render_docs_html. Redoc is
read-only by design -- there is no try-it-out to disable; live calls go
through Postman (Authorization type "AWS Signature") since data routes
require SigV4.
Theme = the SHOC design-system token set (the Sea Haven visual standard):
Montserrat headings / DM Sans body / JetBrains Mono code, primary #1c75bc,
navy #262262, page background #f9fafb, 244px sidebar. Fonts are vendored --
nothing here may fetch from Google Fonts.
feat(api): stock Swagger UI for /docs (vendored offline) (#128) * feat(api): use stock Swagger UI for the /docs page Replaces the custom renderer with vendored stock Swagger UI (swagger-ui-dist 5.17.14, Apache-2.0), kept offline (no CDN) and inlined server-side into the single token-gated /docs response alongside the spec. BaseLayout (topbar hidden); try-it-out disabled since data routes need SigV4 (use Postman for live calls). Breakout guards on the inlined css/js/spec. Bundle-consistency + spec-drift + handler tests updated for the two vendored assets. cdk diff = Lambda code asset only (no IAM/API/policy change). * fix(api): render Swagger UI with the canonical StandaloneLayout recipe The BaseLayout-only init (apis preset, no standalone preset) rendered incorrectly. Switch to the canonical swagger-ui-dist recipe: vendor swagger-ui-standalone-preset.js and init with presets:[apis, SwaggerUIStandalonePreset] + layout:"StandaloneLayout" (topbar hidden, try-it-out disabled). Verified via headless Chrome against the live deployed page: all 9 endpoints + 4 webhooks + models render. Tests/bundling updated for the third vendored asset. * fix(api): declutter the Swagger UI docs page The page rendered (stock Swagger UI, StandaloneLayout) but looked cramped: a dense info.description wall of inline-code chips collided across Swagger UI's tight default line-height, and the Servers box showed a SEE-STACK-OUTPUT placeholder. - Trim info.description to a few concise lines (detail lives in README + the webhook contract doc). - Inject the live invoke URL into servers[0].url per request (from the API Gateway request context; committed literal is the fallback), so the Servers box shows the real endpoint and drops the server-variables section. - CSS: loosen description line-height + inline-code padding so chips never overlap; tidy the scheme container spacing. - Handler: cache the heavy CSS/JS/preset shell once; splice the (server- injected) spec per request. Verified via headless Chrome against the live deployed page.
2026-07-23 20:19:47 -04:00
-->
feat(api): Redocly lint gate + SHOC-themed /docs (Redoc theming, topbar, collapsible samples) (#130) * feat(api): Add @redocly/cli as a dev dependency Signed-off-by: Adam Moussa <adam@seahavenind.com> * feat(api): Add Redocly configuration file with custom rules Signed-off-by: Adam Moussa <adam@seahavenind.com> * chore(api): Redocly lint config + bring openapi.json into compliance redocly.yaml from the Redocly guidelines builder, with three generated rules corrected: response-contains-property had the status codes as the required body fields (intent was the Error schema's top-level 'error'; 403 exempt since API Gateway emits AWS's {message} shape, 501 not 503); operation-4xx-problem-details-rfc7807 off (adopting RFC 7807 would be a runtime + SHOC-contract change, decided against); the two inert casing rules (parameter names, schema properties) removed because both name sets are contract-pinned (gateway resource paths, DynamoDB items). Spec changes, no runtime impact: operationIds renamed to method-prefixed kebab-case (get-work-orders, post-work-order-comment, ...); tags added to all 15 operations + root tags object (groups the Redoc sidebar); examples on all six parameters; license field; server description punctuation; two descriptions reworded to start capitalized. Real linter catches fixed: the two x-planned ops were missing their {workOrderId} path parameter and any 4xx response (403 added - true today, gateway rejects unsigned). .redocly.lint-ignore.yaml pins the six deliberate exceptions: webhook keys are the shipped SHOC contract event names (not renameable), and the x-planned ops answer only 501 (no 2xx to document). package.json: npm run lint:api. Verified: lint 0 errors, 675 pytest, headless-Chrome render of the tagged docs page. * feat(api): SHOC design-system theme for /docs (vendored fonts) Themes the Redoc page with the canonical SHOC token set: Montserrat 600 headings / DM Sans body / JetBrains Mono code, primary #1c75bc, navy #262262 sidebar text + right panel, #f9fafb background, 244px sidebar. sortRequiredPropsFirst on; 200 responses pre-expanded. Fonts ship as lambdas/api/fonts.css (latin woff2 subsets from @fontsource 5.3.0, embedded as data URIs, ~90KB) and inline via a new __FONTS_CSS__ placeholder with the same </style breakout guard -- the offline single-response invariant holds, nothing fetches Google Fonts (test-pinned). Bundling cp + bundle-consistency pin + spec-drift asset checks extended. Verified: headless-Chrome render (theme + fonts applied), ruff, 675 pytest, cdk synth + staged-asset check. * feat(api): SHOC gradient topbar on /docs 64px fixed header with the SHOC shell gradient token (#1b1f52 -> #1c4f8f -> #1c75bc), Sea Haven wordmark in Montserrat 600, page name right-aligned in DM Sans. Redoc's scrollYOffset: 64 keeps the sticky sidebar and anchor scrolling clear of the fixed bar. Verified via headless-Chrome render. * style(api): normalize /docs header and right-panel blues The right panel's #262262 is a purple-leaning navy that clashed with the cyan-leaning gradient, and the bar's brightest point sat directly over the dark panel. Right panel now uses #1b1f52 (the gradient's own dark endpoint) and the gradient runs bright-to-dark so its dark end lands flush on the panel -- no seam, one blue family. Verified via headless-Chrome render. * style(api): right-panel gradient on /docs via bundle-pinned override Redoc's theme only takes solid colors (it derives shades from rightPanel.backgroundColor), so the gradient (#1b3d79 -> #1b3068 -> #1b1f52, continuing the topbar blend) rides as a CSS override on the styled-components classes of the per-section right-panel divs (.sc-iGgWBj.sc-gsFSXq + the .sc-dExYaf stub). Those names are deterministic for the vendored 2.5.3 bundle (verified across loads) but change on any Redoc bump: re-derive via headless probe (find elements whose computed background equals the rightPanel color). If they stop matching, the panel falls back to the solid #1b1f52 theme color -- cosmetic only. Verified via headless-Chrome render. * feat(api): collapsible samples column on /docs Redoc CE has no built-in panel toggle, so the topbar gains a Hide/Show samples button that flips .samples-collapsed on <html>: the right-panel divs hide (same bundle-pinned styled-components classes as the gradient override) and each section's content half takes the full width. Choice persists in localStorage; aria-pressed tracks state. If the pinned classes stop matching after a Redoc bump the toggle goes inert -- cosmetic only. Both states verified via headless-Chrome render. * ci(api): spec-lint CI gate + npm Dependabot coverage New spec-lint job mirrors the local npm run lint:api so openapi.json cannot drift from redocly.yaml with green CI. Dependabot gains the npm ecosystem (package.json is new; nothing watched @redocly/cli). * feat(api): docs finishing touches - x-tagGroups, favicon, docs:preview x-tagGroups sections the Redoc sidebar (Read API / Meta / SHOC Feed); inline data-URI SVG favicon (SHOC blue) stops the browser's follow-up /favicon.ico request 403ing at the gateway; npm run docs:preview wraps the real-handler local render (scripts/preview_docs.py); README gains a docs-page architecture section covering the inline pattern, theme, pinned-selector caveat, and tooling. Lint 0 errors, 675 pytest, headless render verified. --------- Signed-off-by: Adam Moussa <adam@seahavenind.com>
2026-07-24 14:04:30 -04:00
<style>__FONTS_CSS__</style>
feat(api): procurement-api read stack + OpenAPI docs (SHOC reconciliation path) (#127) * feat(api): add procurement-api stack - read API + OpenAPI docs page Third CDK stack: API Gateway REST API (IAM SigV4) over both pipelines' tables, replacing SHOC's retired SyncController cross-account DynamoDB scan as the reconciliation/backfill path. - lambdas/api/: handler (healthcheck + docs-token gate + router dispatch), router (single route table), pagination (opaque cursor, hostile -> 400), Decimal-safe serialization, wo_repo/po_repo reads. No VendorReplies. - OpenAPI 3.1 spec as source of truth incl. top-level webhooks section documenting the outbound SHOC feed; phase-2 write endpoints x-planned (router answers 501). Self-contained /docs page, no CDN. - Auth: AWS_IAM on data routes + resource policy scoped to exactly arn:aws:iam::396287094661:role/shoc-backend-dev on GET/*; /docs and /openapi.json carve-out is token-gated in the Lambda via shared web_ui_auth (fail-closed, INFRA-74 posture). - KMS: explicit Decrypt/DescribeKey on the DynamoDB CMK from SSM (name-imported table drops the key association - INFRA-104 class). - Alarms: errors/throttles/duration(p99>=22.5s) + gateway 5xx, ALARM-only to site-alerts. No access logging in v1 (docs ?token= shim stays out of logs); cloud_watch_role=False. - Tests: handler auth-seam + routing + Decimal round-trip; moto cursor pagination incl. hostile cursors; spec<->router drift gate; bundle AST pins for the api command; pytest.ini --cov + loader siblings. - Deploy role: third stack DescribeStacks ARN + procurement-api smoke invoke ARN (re-run create-deploy-role.sh before merge). * harden(api): apply sh-security-review findings to procurement-api Fan-out (6 detectors) + review findings resolved: Correctness / DoS: - pagination: require EXACT key-set match (was subset) so a partial/foreign composite cursor can't reach DynamoDB as an inconsistent ExclusiveStartKey -> ValidationException -> 500; comments Query now pins the cursor's work_order_id to the path entity. - handler: map botocore ValidationException to 400 (defense in depth) so a crafted cursor can't drive the zero-threshold 5xx alarm. - web_ui_auth: compare tokens as bytes; a non-ASCII presented token now fails closed (401) instead of crashing hmac.compare_digest into a 500. Resolves the pre-existing xfail(strict) follow-up test; hardens the web UIs too. Docs page: - typeStr() now escapes the one spec-derived string that reached innerHTML. - spec inlined into the docs <script> block escapes "<" -> < (</script> breakout guard); /openapi.json still served byte-faithful. - Cache-Control: no-store + Referrer-Policy: no-referrer on docs responses so the ?token= URL stays out of caches/Referer. - spec-drift test asserts the committed spec carries no "</" / "<!--". IAM / IaC: - resource policy enumerates the 7 data GET resources instead of GET/* so a future GET route can't silently inherit SHOC cross-account reach. - kms:Decrypt grant gains a kms:ViaService=dynamodb condition. - stage throttling (50 rps / 100 burst) bounds the unauthenticated /docs blast radius below the 10k account default. - corrected the PATCH/POST comment (same-account callers aren't blocked by the resource policy; 501 handler + absent write grant are the gate). - documented the RETAIN log-group first-deploy rollback trap and the resource-policy-needs-redeploy gotcha in-stack. Mandatory GPT-4.1 cross-family review of the full policy surface: no BLOCK/FIX. 675 tests pass, ruff clean, cdk synth green.
2026-07-23 19:32:20 -04:00
<style>
feat(api): Redocly lint gate + SHOC-themed /docs (Redoc theming, topbar, collapsible samples) (#130) * feat(api): Add @redocly/cli as a dev dependency Signed-off-by: Adam Moussa <adam@seahavenind.com> * feat(api): Add Redocly configuration file with custom rules Signed-off-by: Adam Moussa <adam@seahavenind.com> * chore(api): Redocly lint config + bring openapi.json into compliance redocly.yaml from the Redocly guidelines builder, with three generated rules corrected: response-contains-property had the status codes as the required body fields (intent was the Error schema's top-level 'error'; 403 exempt since API Gateway emits AWS's {message} shape, 501 not 503); operation-4xx-problem-details-rfc7807 off (adopting RFC 7807 would be a runtime + SHOC-contract change, decided against); the two inert casing rules (parameter names, schema properties) removed because both name sets are contract-pinned (gateway resource paths, DynamoDB items). Spec changes, no runtime impact: operationIds renamed to method-prefixed kebab-case (get-work-orders, post-work-order-comment, ...); tags added to all 15 operations + root tags object (groups the Redoc sidebar); examples on all six parameters; license field; server description punctuation; two descriptions reworded to start capitalized. Real linter catches fixed: the two x-planned ops were missing their {workOrderId} path parameter and any 4xx response (403 added - true today, gateway rejects unsigned). .redocly.lint-ignore.yaml pins the six deliberate exceptions: webhook keys are the shipped SHOC contract event names (not renameable), and the x-planned ops answer only 501 (no 2xx to document). package.json: npm run lint:api. Verified: lint 0 errors, 675 pytest, headless-Chrome render of the tagged docs page. * feat(api): SHOC design-system theme for /docs (vendored fonts) Themes the Redoc page with the canonical SHOC token set: Montserrat 600 headings / DM Sans body / JetBrains Mono code, primary #1c75bc, navy #262262 sidebar text + right panel, #f9fafb background, 244px sidebar. sortRequiredPropsFirst on; 200 responses pre-expanded. Fonts ship as lambdas/api/fonts.css (latin woff2 subsets from @fontsource 5.3.0, embedded as data URIs, ~90KB) and inline via a new __FONTS_CSS__ placeholder with the same </style breakout guard -- the offline single-response invariant holds, nothing fetches Google Fonts (test-pinned). Bundling cp + bundle-consistency pin + spec-drift asset checks extended. Verified: headless-Chrome render (theme + fonts applied), ruff, 675 pytest, cdk synth + staged-asset check. * feat(api): SHOC gradient topbar on /docs 64px fixed header with the SHOC shell gradient token (#1b1f52 -> #1c4f8f -> #1c75bc), Sea Haven wordmark in Montserrat 600, page name right-aligned in DM Sans. Redoc's scrollYOffset: 64 keeps the sticky sidebar and anchor scrolling clear of the fixed bar. Verified via headless-Chrome render. * style(api): normalize /docs header and right-panel blues The right panel's #262262 is a purple-leaning navy that clashed with the cyan-leaning gradient, and the bar's brightest point sat directly over the dark panel. Right panel now uses #1b1f52 (the gradient's own dark endpoint) and the gradient runs bright-to-dark so its dark end lands flush on the panel -- no seam, one blue family. Verified via headless-Chrome render. * style(api): right-panel gradient on /docs via bundle-pinned override Redoc's theme only takes solid colors (it derives shades from rightPanel.backgroundColor), so the gradient (#1b3d79 -> #1b3068 -> #1b1f52, continuing the topbar blend) rides as a CSS override on the styled-components classes of the per-section right-panel divs (.sc-iGgWBj.sc-gsFSXq + the .sc-dExYaf stub). Those names are deterministic for the vendored 2.5.3 bundle (verified across loads) but change on any Redoc bump: re-derive via headless probe (find elements whose computed background equals the rightPanel color). If they stop matching, the panel falls back to the solid #1b1f52 theme color -- cosmetic only. Verified via headless-Chrome render. * feat(api): collapsible samples column on /docs Redoc CE has no built-in panel toggle, so the topbar gains a Hide/Show samples button that flips .samples-collapsed on <html>: the right-panel divs hide (same bundle-pinned styled-components classes as the gradient override) and each section's content half takes the full width. Choice persists in localStorage; aria-pressed tracks state. If the pinned classes stop matching after a Redoc bump the toggle goes inert -- cosmetic only. Both states verified via headless-Chrome render. * ci(api): spec-lint CI gate + npm Dependabot coverage New spec-lint job mirrors the local npm run lint:api so openapi.json cannot drift from redocly.yaml with green CI. Dependabot gains the npm ecosystem (package.json is new; nothing watched @redocly/cli). * feat(api): docs finishing touches - x-tagGroups, favicon, docs:preview x-tagGroups sections the Redoc sidebar (Read API / Meta / SHOC Feed); inline data-URI SVG favicon (SHOC blue) stops the browser's follow-up /favicon.ico request 403ing at the gateway; npm run docs:preview wraps the real-handler local render (scripts/preview_docs.py); README gains a docs-page architecture section covering the inline pattern, theme, pinned-selector caveat, and tooling. Lint 0 errors, 675 pytest, headless render verified. --------- Signed-off-by: Adam Moussa <adam@seahavenind.com>
2026-07-24 14:04:30 -04:00
html, body { margin: 0; padding: 0; background: #f9fafb; }
/* SHOC shell topbar: 64px, gradient header token. Redoc has no topbar of
its own; scrollYOffset below keeps its sticky sidebar/scroll accounting
clear of this fixed bar. Gradient runs bright-to-dark (reversed from the
SHOC shell) so its dark end (#1b1f52) sits flush over the right panel,
which uses the same color -- one continuous blue family, no seam. */
#topbar {
position: fixed;
top: 0;
left: 0;
right: 0;
height: 64px;
z-index: 10;
display: flex;
align-items: center;
padding: 0 24px;
box-sizing: border-box;
background: linear-gradient(90deg, #1c75bc 0%, #1c4f8f 45%, #1b1f52 100%);
color: #ffffff;
font-family: "Montserrat", system-ui, -apple-system, "Segoe UI", Helvetica, Arial, sans-serif;
}
#topbar .brand {
font-size: 16px;
font-weight: 600;
letter-spacing: 0.02em;
}
#topbar .page {
margin-left: 16px;
font-family: "DM Sans", system-ui, -apple-system, "Segoe UI", Helvetica, Arial, sans-serif;
font-size: 13px;
font-weight: 400;
opacity: 0.85;
}
#samples-toggle {
margin-left: auto;
padding: 5px 14px;
background: transparent;
color: #ffffff;
border: 1px solid rgba(255, 255, 255, 0.45);
border-radius: 6px;
font-family: "DM Sans", system-ui, -apple-system, "Segoe UI", Helvetica, Arial, sans-serif;
font-size: 12px;
cursor: pointer;
}
#samples-toggle:hover { border-color: #ffffff; background: rgba(255, 255, 255, 0.1); }
#redoc { padding-top: 64px; }
/* Collapsible samples column: Redoc CE has no built-in panel toggle, so
the topbar button flips .samples-collapsed on <html>, hiding the
right-panel divs (same bundle-pinned classes as the gradient below)
and letting each section's content half take the full width. If the
pinned classes stop matching after a Redoc bump the toggle goes inert
-- cosmetic only. Choice persists in localStorage. */
html.samples-collapsed .sc-iGgWBj.sc-gsFSXq,
html.samples-collapsed div.sc-dExYaf { display: none; }
html.samples-collapsed [data-section-id] > div:first-child { width: 100%; }
/* Right-panel gradient (#1b3d79 -> #1b1f52, continuing the topbar blend).
Redoc's theme only accepts solid colors (it derives shades from
rightPanel.backgroundColor), so the solid #1b1f52 stays in the theme as
the fallback and this override layers the gradient on top.
BUNDLE-PINNED SELECTORS: .sc-iGgWBj.sc-gsFSXq are the styled-components
classes of the per-section right-panel divs (plus .sc-dExYaf, the
full-height background stub) generated by the vendored
redoc.standalone.js 2.5.3. They are deterministic for this exact bundle
but WILL change on any Redoc bump -- re-derive them then (headless probe:
find elements whose computed background is the rightPanel color). If
they stop matching, the panel silently falls back to solid #1b1f52 --
cosmetic only, nothing breaks. Every stripe shares the same horizontal
geometry, so the per-section gradients read as one continuous column. */
.sc-iGgWBj.sc-gsFSXq,
div.sc-dExYaf {
background-image: linear-gradient(90deg, #1b3d79 0%, #1b3068 45%, #1b1f52 100%);
}
feat(api): procurement-api read stack + OpenAPI docs (SHOC reconciliation path) (#127) * feat(api): add procurement-api stack - read API + OpenAPI docs page Third CDK stack: API Gateway REST API (IAM SigV4) over both pipelines' tables, replacing SHOC's retired SyncController cross-account DynamoDB scan as the reconciliation/backfill path. - lambdas/api/: handler (healthcheck + docs-token gate + router dispatch), router (single route table), pagination (opaque cursor, hostile -> 400), Decimal-safe serialization, wo_repo/po_repo reads. No VendorReplies. - OpenAPI 3.1 spec as source of truth incl. top-level webhooks section documenting the outbound SHOC feed; phase-2 write endpoints x-planned (router answers 501). Self-contained /docs page, no CDN. - Auth: AWS_IAM on data routes + resource policy scoped to exactly arn:aws:iam::396287094661:role/shoc-backend-dev on GET/*; /docs and /openapi.json carve-out is token-gated in the Lambda via shared web_ui_auth (fail-closed, INFRA-74 posture). - KMS: explicit Decrypt/DescribeKey on the DynamoDB CMK from SSM (name-imported table drops the key association - INFRA-104 class). - Alarms: errors/throttles/duration(p99>=22.5s) + gateway 5xx, ALARM-only to site-alerts. No access logging in v1 (docs ?token= shim stays out of logs); cloud_watch_role=False. - Tests: handler auth-seam + routing + Decimal round-trip; moto cursor pagination incl. hostile cursors; spec<->router drift gate; bundle AST pins for the api command; pytest.ini --cov + loader siblings. - Deploy role: third stack DescribeStacks ARN + procurement-api smoke invoke ARN (re-run create-deploy-role.sh before merge). * harden(api): apply sh-security-review findings to procurement-api Fan-out (6 detectors) + review findings resolved: Correctness / DoS: - pagination: require EXACT key-set match (was subset) so a partial/foreign composite cursor can't reach DynamoDB as an inconsistent ExclusiveStartKey -> ValidationException -> 500; comments Query now pins the cursor's work_order_id to the path entity. - handler: map botocore ValidationException to 400 (defense in depth) so a crafted cursor can't drive the zero-threshold 5xx alarm. - web_ui_auth: compare tokens as bytes; a non-ASCII presented token now fails closed (401) instead of crashing hmac.compare_digest into a 500. Resolves the pre-existing xfail(strict) follow-up test; hardens the web UIs too. Docs page: - typeStr() now escapes the one spec-derived string that reached innerHTML. - spec inlined into the docs <script> block escapes "<" -> < (</script> breakout guard); /openapi.json still served byte-faithful. - Cache-Control: no-store + Referrer-Policy: no-referrer on docs responses so the ?token= URL stays out of caches/Referer. - spec-drift test asserts the committed spec carries no "</" / "<!--". IAM / IaC: - resource policy enumerates the 7 data GET resources instead of GET/* so a future GET route can't silently inherit SHOC cross-account reach. - kms:Decrypt grant gains a kms:ViaService=dynamodb condition. - stage throttling (50 rps / 100 burst) bounds the unauthenticated /docs blast radius below the 10k account default. - corrected the PATCH/POST comment (same-account callers aren't blocked by the resource policy; 501 handler + absent write grant are the gate). - documented the RETAIN log-group first-deploy rollback trap and the resource-policy-needs-redeploy gotcha in-stack. Mandatory GPT-4.1 cross-family review of the full policy surface: no BLOCK/FIX. 675 tests pass, ruff clean, cdk synth green.
2026-07-23 19:32:20 -04:00
</style>
</head>
<body>
feat(api): Redocly lint gate + SHOC-themed /docs (Redoc theming, topbar, collapsible samples) (#130) * feat(api): Add @redocly/cli as a dev dependency Signed-off-by: Adam Moussa <adam@seahavenind.com> * feat(api): Add Redocly configuration file with custom rules Signed-off-by: Adam Moussa <adam@seahavenind.com> * chore(api): Redocly lint config + bring openapi.json into compliance redocly.yaml from the Redocly guidelines builder, with three generated rules corrected: response-contains-property had the status codes as the required body fields (intent was the Error schema's top-level 'error'; 403 exempt since API Gateway emits AWS's {message} shape, 501 not 503); operation-4xx-problem-details-rfc7807 off (adopting RFC 7807 would be a runtime + SHOC-contract change, decided against); the two inert casing rules (parameter names, schema properties) removed because both name sets are contract-pinned (gateway resource paths, DynamoDB items). Spec changes, no runtime impact: operationIds renamed to method-prefixed kebab-case (get-work-orders, post-work-order-comment, ...); tags added to all 15 operations + root tags object (groups the Redoc sidebar); examples on all six parameters; license field; server description punctuation; two descriptions reworded to start capitalized. Real linter catches fixed: the two x-planned ops were missing their {workOrderId} path parameter and any 4xx response (403 added - true today, gateway rejects unsigned). .redocly.lint-ignore.yaml pins the six deliberate exceptions: webhook keys are the shipped SHOC contract event names (not renameable), and the x-planned ops answer only 501 (no 2xx to document). package.json: npm run lint:api. Verified: lint 0 errors, 675 pytest, headless-Chrome render of the tagged docs page. * feat(api): SHOC design-system theme for /docs (vendored fonts) Themes the Redoc page with the canonical SHOC token set: Montserrat 600 headings / DM Sans body / JetBrains Mono code, primary #1c75bc, navy #262262 sidebar text + right panel, #f9fafb background, 244px sidebar. sortRequiredPropsFirst on; 200 responses pre-expanded. Fonts ship as lambdas/api/fonts.css (latin woff2 subsets from @fontsource 5.3.0, embedded as data URIs, ~90KB) and inline via a new __FONTS_CSS__ placeholder with the same </style breakout guard -- the offline single-response invariant holds, nothing fetches Google Fonts (test-pinned). Bundling cp + bundle-consistency pin + spec-drift asset checks extended. Verified: headless-Chrome render (theme + fonts applied), ruff, 675 pytest, cdk synth + staged-asset check. * feat(api): SHOC gradient topbar on /docs 64px fixed header with the SHOC shell gradient token (#1b1f52 -> #1c4f8f -> #1c75bc), Sea Haven wordmark in Montserrat 600, page name right-aligned in DM Sans. Redoc's scrollYOffset: 64 keeps the sticky sidebar and anchor scrolling clear of the fixed bar. Verified via headless-Chrome render. * style(api): normalize /docs header and right-panel blues The right panel's #262262 is a purple-leaning navy that clashed with the cyan-leaning gradient, and the bar's brightest point sat directly over the dark panel. Right panel now uses #1b1f52 (the gradient's own dark endpoint) and the gradient runs bright-to-dark so its dark end lands flush on the panel -- no seam, one blue family. Verified via headless-Chrome render. * style(api): right-panel gradient on /docs via bundle-pinned override Redoc's theme only takes solid colors (it derives shades from rightPanel.backgroundColor), so the gradient (#1b3d79 -> #1b3068 -> #1b1f52, continuing the topbar blend) rides as a CSS override on the styled-components classes of the per-section right-panel divs (.sc-iGgWBj.sc-gsFSXq + the .sc-dExYaf stub). Those names are deterministic for the vendored 2.5.3 bundle (verified across loads) but change on any Redoc bump: re-derive via headless probe (find elements whose computed background equals the rightPanel color). If they stop matching, the panel falls back to the solid #1b1f52 theme color -- cosmetic only. Verified via headless-Chrome render. * feat(api): collapsible samples column on /docs Redoc CE has no built-in panel toggle, so the topbar gains a Hide/Show samples button that flips .samples-collapsed on <html>: the right-panel divs hide (same bundle-pinned styled-components classes as the gradient override) and each section's content half takes the full width. Choice persists in localStorage; aria-pressed tracks state. If the pinned classes stop matching after a Redoc bump the toggle goes inert -- cosmetic only. Both states verified via headless-Chrome render. * ci(api): spec-lint CI gate + npm Dependabot coverage New spec-lint job mirrors the local npm run lint:api so openapi.json cannot drift from redocly.yaml with green CI. Dependabot gains the npm ecosystem (package.json is new; nothing watched @redocly/cli). * feat(api): docs finishing touches - x-tagGroups, favicon, docs:preview x-tagGroups sections the Redoc sidebar (Read API / Meta / SHOC Feed); inline data-URI SVG favicon (SHOC blue) stops the browser's follow-up /favicon.ico request 403ing at the gateway; npm run docs:preview wraps the real-handler local render (scripts/preview_docs.py); README gains a docs-page architecture section covering the inline pattern, theme, pinned-selector caveat, and tooling. Lint 0 errors, 675 pytest, headless render verified. --------- Signed-off-by: Adam Moussa <adam@seahavenind.com>
2026-07-24 14:04:30 -04:00
<div id="topbar">
<span class="brand">Sea Haven Industries</span>
<button id="samples-toggle" type="button" aria-pressed="false">Hide samples</button>
<span class="page">Procurement Ingest API</span>
</div>
<div id="redoc"></div>
<script>__REDOC_JS__</script>
feat(api): procurement-api read stack + OpenAPI docs (SHOC reconciliation path) (#127) * feat(api): add procurement-api stack - read API + OpenAPI docs page Third CDK stack: API Gateway REST API (IAM SigV4) over both pipelines' tables, replacing SHOC's retired SyncController cross-account DynamoDB scan as the reconciliation/backfill path. - lambdas/api/: handler (healthcheck + docs-token gate + router dispatch), router (single route table), pagination (opaque cursor, hostile -> 400), Decimal-safe serialization, wo_repo/po_repo reads. No VendorReplies. - OpenAPI 3.1 spec as source of truth incl. top-level webhooks section documenting the outbound SHOC feed; phase-2 write endpoints x-planned (router answers 501). Self-contained /docs page, no CDN. - Auth: AWS_IAM on data routes + resource policy scoped to exactly arn:aws:iam::396287094661:role/shoc-backend-dev on GET/*; /docs and /openapi.json carve-out is token-gated in the Lambda via shared web_ui_auth (fail-closed, INFRA-74 posture). - KMS: explicit Decrypt/DescribeKey on the DynamoDB CMK from SSM (name-imported table drops the key association - INFRA-104 class). - Alarms: errors/throttles/duration(p99>=22.5s) + gateway 5xx, ALARM-only to site-alerts. No access logging in v1 (docs ?token= shim stays out of logs); cloud_watch_role=False. - Tests: handler auth-seam + routing + Decimal round-trip; moto cursor pagination incl. hostile cursors; spec<->router drift gate; bundle AST pins for the api command; pytest.ini --cov + loader siblings. - Deploy role: third stack DescribeStacks ARN + procurement-api smoke invoke ARN (re-run create-deploy-role.sh before merge). * harden(api): apply sh-security-review findings to procurement-api Fan-out (6 detectors) + review findings resolved: Correctness / DoS: - pagination: require EXACT key-set match (was subset) so a partial/foreign composite cursor can't reach DynamoDB as an inconsistent ExclusiveStartKey -> ValidationException -> 500; comments Query now pins the cursor's work_order_id to the path entity. - handler: map botocore ValidationException to 400 (defense in depth) so a crafted cursor can't drive the zero-threshold 5xx alarm. - web_ui_auth: compare tokens as bytes; a non-ASCII presented token now fails closed (401) instead of crashing hmac.compare_digest into a 500. Resolves the pre-existing xfail(strict) follow-up test; hardens the web UIs too. Docs page: - typeStr() now escapes the one spec-derived string that reached innerHTML. - spec inlined into the docs <script> block escapes "<" -> < (</script> breakout guard); /openapi.json still served byte-faithful. - Cache-Control: no-store + Referrer-Policy: no-referrer on docs responses so the ?token= URL stays out of caches/Referer. - spec-drift test asserts the committed spec carries no "</" / "<!--". IAM / IaC: - resource policy enumerates the 7 data GET resources instead of GET/* so a future GET route can't silently inherit SHOC cross-account reach. - kms:Decrypt grant gains a kms:ViaService=dynamodb condition. - stage throttling (50 rps / 100 burst) bounds the unauthenticated /docs blast radius below the 10k account default. - corrected the PATCH/POST comment (same-account callers aren't blocked by the resource policy; 501 handler + absent write grant are the gate). - documented the RETAIN log-group first-deploy rollback trap and the resource-policy-needs-redeploy gotcha in-stack. Mandatory GPT-4.1 cross-family review of the full policy surface: no BLOCK/FIX. 675 tests pass, ruff clean, cdk synth green.
2026-07-23 19:32:20 -04:00
<script>
Redoc.init(
__OPENAPI_SPEC_JSON__,
{
feat(api): Redocly lint gate + SHOC-themed /docs (Redoc theming, topbar, collapsible samples) (#130) * feat(api): Add @redocly/cli as a dev dependency Signed-off-by: Adam Moussa <adam@seahavenind.com> * feat(api): Add Redocly configuration file with custom rules Signed-off-by: Adam Moussa <adam@seahavenind.com> * chore(api): Redocly lint config + bring openapi.json into compliance redocly.yaml from the Redocly guidelines builder, with three generated rules corrected: response-contains-property had the status codes as the required body fields (intent was the Error schema's top-level 'error'; 403 exempt since API Gateway emits AWS's {message} shape, 501 not 503); operation-4xx-problem-details-rfc7807 off (adopting RFC 7807 would be a runtime + SHOC-contract change, decided against); the two inert casing rules (parameter names, schema properties) removed because both name sets are contract-pinned (gateway resource paths, DynamoDB items). Spec changes, no runtime impact: operationIds renamed to method-prefixed kebab-case (get-work-orders, post-work-order-comment, ...); tags added to all 15 operations + root tags object (groups the Redoc sidebar); examples on all six parameters; license field; server description punctuation; two descriptions reworded to start capitalized. Real linter catches fixed: the two x-planned ops were missing their {workOrderId} path parameter and any 4xx response (403 added - true today, gateway rejects unsigned). .redocly.lint-ignore.yaml pins the six deliberate exceptions: webhook keys are the shipped SHOC contract event names (not renameable), and the x-planned ops answer only 501 (no 2xx to document). package.json: npm run lint:api. Verified: lint 0 errors, 675 pytest, headless-Chrome render of the tagged docs page. * feat(api): SHOC design-system theme for /docs (vendored fonts) Themes the Redoc page with the canonical SHOC token set: Montserrat 600 headings / DM Sans body / JetBrains Mono code, primary #1c75bc, navy #262262 sidebar text + right panel, #f9fafb background, 244px sidebar. sortRequiredPropsFirst on; 200 responses pre-expanded. Fonts ship as lambdas/api/fonts.css (latin woff2 subsets from @fontsource 5.3.0, embedded as data URIs, ~90KB) and inline via a new __FONTS_CSS__ placeholder with the same </style breakout guard -- the offline single-response invariant holds, nothing fetches Google Fonts (test-pinned). Bundling cp + bundle-consistency pin + spec-drift asset checks extended. Verified: headless-Chrome render (theme + fonts applied), ruff, 675 pytest, cdk synth + staged-asset check. * feat(api): SHOC gradient topbar on /docs 64px fixed header with the SHOC shell gradient token (#1b1f52 -> #1c4f8f -> #1c75bc), Sea Haven wordmark in Montserrat 600, page name right-aligned in DM Sans. Redoc's scrollYOffset: 64 keeps the sticky sidebar and anchor scrolling clear of the fixed bar. Verified via headless-Chrome render. * style(api): normalize /docs header and right-panel blues The right panel's #262262 is a purple-leaning navy that clashed with the cyan-leaning gradient, and the bar's brightest point sat directly over the dark panel. Right panel now uses #1b1f52 (the gradient's own dark endpoint) and the gradient runs bright-to-dark so its dark end lands flush on the panel -- no seam, one blue family. Verified via headless-Chrome render. * style(api): right-panel gradient on /docs via bundle-pinned override Redoc's theme only takes solid colors (it derives shades from rightPanel.backgroundColor), so the gradient (#1b3d79 -> #1b3068 -> #1b1f52, continuing the topbar blend) rides as a CSS override on the styled-components classes of the per-section right-panel divs (.sc-iGgWBj.sc-gsFSXq + the .sc-dExYaf stub). Those names are deterministic for the vendored 2.5.3 bundle (verified across loads) but change on any Redoc bump: re-derive via headless probe (find elements whose computed background equals the rightPanel color). If they stop matching, the panel falls back to the solid #1b1f52 theme color -- cosmetic only. Verified via headless-Chrome render. * feat(api): collapsible samples column on /docs Redoc CE has no built-in panel toggle, so the topbar gains a Hide/Show samples button that flips .samples-collapsed on <html>: the right-panel divs hide (same bundle-pinned styled-components classes as the gradient override) and each section's content half takes the full width. Choice persists in localStorage; aria-pressed tracks state. If the pinned classes stop matching after a Redoc bump the toggle goes inert -- cosmetic only. Both states verified via headless-Chrome render. * ci(api): spec-lint CI gate + npm Dependabot coverage New spec-lint job mirrors the local npm run lint:api so openapi.json cannot drift from redocly.yaml with green CI. Dependabot gains the npm ecosystem (package.json is new; nothing watched @redocly/cli). * feat(api): docs finishing touches - x-tagGroups, favicon, docs:preview x-tagGroups sections the Redoc sidebar (Read API / Meta / SHOC Feed); inline data-URI SVG favicon (SHOC blue) stops the browser's follow-up /favicon.ico request 403ing at the gateway; npm run docs:preview wraps the real-handler local render (scripts/preview_docs.py); README gains a docs-page architecture section covering the inline pattern, theme, pinned-selector caveat, and tooling. Lint 0 errors, 675 pytest, headless render verified. --------- Signed-off-by: Adam Moussa <adam@seahavenind.com>
2026-07-24 14:04:30 -04:00
sortRequiredPropsFirst: true,
expandResponses: "200",
scrollYOffset: 64,
theme: {
feat(api): Redocly lint gate + SHOC-themed /docs (Redoc theming, topbar, collapsible samples) (#130) * feat(api): Add @redocly/cli as a dev dependency Signed-off-by: Adam Moussa <adam@seahavenind.com> * feat(api): Add Redocly configuration file with custom rules Signed-off-by: Adam Moussa <adam@seahavenind.com> * chore(api): Redocly lint config + bring openapi.json into compliance redocly.yaml from the Redocly guidelines builder, with three generated rules corrected: response-contains-property had the status codes as the required body fields (intent was the Error schema's top-level 'error'; 403 exempt since API Gateway emits AWS's {message} shape, 501 not 503); operation-4xx-problem-details-rfc7807 off (adopting RFC 7807 would be a runtime + SHOC-contract change, decided against); the two inert casing rules (parameter names, schema properties) removed because both name sets are contract-pinned (gateway resource paths, DynamoDB items). Spec changes, no runtime impact: operationIds renamed to method-prefixed kebab-case (get-work-orders, post-work-order-comment, ...); tags added to all 15 operations + root tags object (groups the Redoc sidebar); examples on all six parameters; license field; server description punctuation; two descriptions reworded to start capitalized. Real linter catches fixed: the two x-planned ops were missing their {workOrderId} path parameter and any 4xx response (403 added - true today, gateway rejects unsigned). .redocly.lint-ignore.yaml pins the six deliberate exceptions: webhook keys are the shipped SHOC contract event names (not renameable), and the x-planned ops answer only 501 (no 2xx to document). package.json: npm run lint:api. Verified: lint 0 errors, 675 pytest, headless-Chrome render of the tagged docs page. * feat(api): SHOC design-system theme for /docs (vendored fonts) Themes the Redoc page with the canonical SHOC token set: Montserrat 600 headings / DM Sans body / JetBrains Mono code, primary #1c75bc, navy #262262 sidebar text + right panel, #f9fafb background, 244px sidebar. sortRequiredPropsFirst on; 200 responses pre-expanded. Fonts ship as lambdas/api/fonts.css (latin woff2 subsets from @fontsource 5.3.0, embedded as data URIs, ~90KB) and inline via a new __FONTS_CSS__ placeholder with the same </style breakout guard -- the offline single-response invariant holds, nothing fetches Google Fonts (test-pinned). Bundling cp + bundle-consistency pin + spec-drift asset checks extended. Verified: headless-Chrome render (theme + fonts applied), ruff, 675 pytest, cdk synth + staged-asset check. * feat(api): SHOC gradient topbar on /docs 64px fixed header with the SHOC shell gradient token (#1b1f52 -> #1c4f8f -> #1c75bc), Sea Haven wordmark in Montserrat 600, page name right-aligned in DM Sans. Redoc's scrollYOffset: 64 keeps the sticky sidebar and anchor scrolling clear of the fixed bar. Verified via headless-Chrome render. * style(api): normalize /docs header and right-panel blues The right panel's #262262 is a purple-leaning navy that clashed with the cyan-leaning gradient, and the bar's brightest point sat directly over the dark panel. Right panel now uses #1b1f52 (the gradient's own dark endpoint) and the gradient runs bright-to-dark so its dark end lands flush on the panel -- no seam, one blue family. Verified via headless-Chrome render. * style(api): right-panel gradient on /docs via bundle-pinned override Redoc's theme only takes solid colors (it derives shades from rightPanel.backgroundColor), so the gradient (#1b3d79 -> #1b3068 -> #1b1f52, continuing the topbar blend) rides as a CSS override on the styled-components classes of the per-section right-panel divs (.sc-iGgWBj.sc-gsFSXq + the .sc-dExYaf stub). Those names are deterministic for the vendored 2.5.3 bundle (verified across loads) but change on any Redoc bump: re-derive via headless probe (find elements whose computed background equals the rightPanel color). If they stop matching, the panel falls back to the solid #1b1f52 theme color -- cosmetic only. Verified via headless-Chrome render. * feat(api): collapsible samples column on /docs Redoc CE has no built-in panel toggle, so the topbar gains a Hide/Show samples button that flips .samples-collapsed on <html>: the right-panel divs hide (same bundle-pinned styled-components classes as the gradient override) and each section's content half takes the full width. Choice persists in localStorage; aria-pressed tracks state. If the pinned classes stop matching after a Redoc bump the toggle goes inert -- cosmetic only. Both states verified via headless-Chrome render. * ci(api): spec-lint CI gate + npm Dependabot coverage New spec-lint job mirrors the local npm run lint:api so openapi.json cannot drift from redocly.yaml with green CI. Dependabot gains the npm ecosystem (package.json is new; nothing watched @redocly/cli). * feat(api): docs finishing touches - x-tagGroups, favicon, docs:preview x-tagGroups sections the Redoc sidebar (Read API / Meta / SHOC Feed); inline data-URI SVG favicon (SHOC blue) stops the browser's follow-up /favicon.ico request 403ing at the gateway; npm run docs:preview wraps the real-handler local render (scripts/preview_docs.py); README gains a docs-page architecture section covering the inline pattern, theme, pinned-selector caveat, and tooling. Lint 0 errors, 675 pytest, headless render verified. --------- Signed-off-by: Adam Moussa <adam@seahavenind.com>
2026-07-24 14:04:30 -04:00
colors: {
primary: { main: "#1c75bc" }
},
typography: {
feat(api): Redocly lint gate + SHOC-themed /docs (Redoc theming, topbar, collapsible samples) (#130) * feat(api): Add @redocly/cli as a dev dependency Signed-off-by: Adam Moussa <adam@seahavenind.com> * feat(api): Add Redocly configuration file with custom rules Signed-off-by: Adam Moussa <adam@seahavenind.com> * chore(api): Redocly lint config + bring openapi.json into compliance redocly.yaml from the Redocly guidelines builder, with three generated rules corrected: response-contains-property had the status codes as the required body fields (intent was the Error schema's top-level 'error'; 403 exempt since API Gateway emits AWS's {message} shape, 501 not 503); operation-4xx-problem-details-rfc7807 off (adopting RFC 7807 would be a runtime + SHOC-contract change, decided against); the two inert casing rules (parameter names, schema properties) removed because both name sets are contract-pinned (gateway resource paths, DynamoDB items). Spec changes, no runtime impact: operationIds renamed to method-prefixed kebab-case (get-work-orders, post-work-order-comment, ...); tags added to all 15 operations + root tags object (groups the Redoc sidebar); examples on all six parameters; license field; server description punctuation; two descriptions reworded to start capitalized. Real linter catches fixed: the two x-planned ops were missing their {workOrderId} path parameter and any 4xx response (403 added - true today, gateway rejects unsigned). .redocly.lint-ignore.yaml pins the six deliberate exceptions: webhook keys are the shipped SHOC contract event names (not renameable), and the x-planned ops answer only 501 (no 2xx to document). package.json: npm run lint:api. Verified: lint 0 errors, 675 pytest, headless-Chrome render of the tagged docs page. * feat(api): SHOC design-system theme for /docs (vendored fonts) Themes the Redoc page with the canonical SHOC token set: Montserrat 600 headings / DM Sans body / JetBrains Mono code, primary #1c75bc, navy #262262 sidebar text + right panel, #f9fafb background, 244px sidebar. sortRequiredPropsFirst on; 200 responses pre-expanded. Fonts ship as lambdas/api/fonts.css (latin woff2 subsets from @fontsource 5.3.0, embedded as data URIs, ~90KB) and inline via a new __FONTS_CSS__ placeholder with the same </style breakout guard -- the offline single-response invariant holds, nothing fetches Google Fonts (test-pinned). Bundling cp + bundle-consistency pin + spec-drift asset checks extended. Verified: headless-Chrome render (theme + fonts applied), ruff, 675 pytest, cdk synth + staged-asset check. * feat(api): SHOC gradient topbar on /docs 64px fixed header with the SHOC shell gradient token (#1b1f52 -> #1c4f8f -> #1c75bc), Sea Haven wordmark in Montserrat 600, page name right-aligned in DM Sans. Redoc's scrollYOffset: 64 keeps the sticky sidebar and anchor scrolling clear of the fixed bar. Verified via headless-Chrome render. * style(api): normalize /docs header and right-panel blues The right panel's #262262 is a purple-leaning navy that clashed with the cyan-leaning gradient, and the bar's brightest point sat directly over the dark panel. Right panel now uses #1b1f52 (the gradient's own dark endpoint) and the gradient runs bright-to-dark so its dark end lands flush on the panel -- no seam, one blue family. Verified via headless-Chrome render. * style(api): right-panel gradient on /docs via bundle-pinned override Redoc's theme only takes solid colors (it derives shades from rightPanel.backgroundColor), so the gradient (#1b3d79 -> #1b3068 -> #1b1f52, continuing the topbar blend) rides as a CSS override on the styled-components classes of the per-section right-panel divs (.sc-iGgWBj.sc-gsFSXq + the .sc-dExYaf stub). Those names are deterministic for the vendored 2.5.3 bundle (verified across loads) but change on any Redoc bump: re-derive via headless probe (find elements whose computed background equals the rightPanel color). If they stop matching, the panel falls back to the solid #1b1f52 theme color -- cosmetic only. Verified via headless-Chrome render. * feat(api): collapsible samples column on /docs Redoc CE has no built-in panel toggle, so the topbar gains a Hide/Show samples button that flips .samples-collapsed on <html>: the right-panel divs hide (same bundle-pinned styled-components classes as the gradient override) and each section's content half takes the full width. Choice persists in localStorage; aria-pressed tracks state. If the pinned classes stop matching after a Redoc bump the toggle goes inert -- cosmetic only. Both states verified via headless-Chrome render. * ci(api): spec-lint CI gate + npm Dependabot coverage New spec-lint job mirrors the local npm run lint:api so openapi.json cannot drift from redocly.yaml with green CI. Dependabot gains the npm ecosystem (package.json is new; nothing watched @redocly/cli). * feat(api): docs finishing touches - x-tagGroups, favicon, docs:preview x-tagGroups sections the Redoc sidebar (Read API / Meta / SHOC Feed); inline data-URI SVG favicon (SHOC blue) stops the browser's follow-up /favicon.ico request 403ing at the gateway; npm run docs:preview wraps the real-handler local render (scripts/preview_docs.py); README gains a docs-page architecture section covering the inline pattern, theme, pinned-selector caveat, and tooling. Lint 0 errors, 675 pytest, headless render verified. --------- Signed-off-by: Adam Moussa <adam@seahavenind.com>
2026-07-24 14:04:30 -04:00
fontFamily: '"DM Sans", system-ui, -apple-system, "Segoe UI", Helvetica, Arial, sans-serif',
fontWeightBold: "600",
headings: {
feat(api): Redocly lint gate + SHOC-themed /docs (Redoc theming, topbar, collapsible samples) (#130) * feat(api): Add @redocly/cli as a dev dependency Signed-off-by: Adam Moussa <adam@seahavenind.com> * feat(api): Add Redocly configuration file with custom rules Signed-off-by: Adam Moussa <adam@seahavenind.com> * chore(api): Redocly lint config + bring openapi.json into compliance redocly.yaml from the Redocly guidelines builder, with three generated rules corrected: response-contains-property had the status codes as the required body fields (intent was the Error schema's top-level 'error'; 403 exempt since API Gateway emits AWS's {message} shape, 501 not 503); operation-4xx-problem-details-rfc7807 off (adopting RFC 7807 would be a runtime + SHOC-contract change, decided against); the two inert casing rules (parameter names, schema properties) removed because both name sets are contract-pinned (gateway resource paths, DynamoDB items). Spec changes, no runtime impact: operationIds renamed to method-prefixed kebab-case (get-work-orders, post-work-order-comment, ...); tags added to all 15 operations + root tags object (groups the Redoc sidebar); examples on all six parameters; license field; server description punctuation; two descriptions reworded to start capitalized. Real linter catches fixed: the two x-planned ops were missing their {workOrderId} path parameter and any 4xx response (403 added - true today, gateway rejects unsigned). .redocly.lint-ignore.yaml pins the six deliberate exceptions: webhook keys are the shipped SHOC contract event names (not renameable), and the x-planned ops answer only 501 (no 2xx to document). package.json: npm run lint:api. Verified: lint 0 errors, 675 pytest, headless-Chrome render of the tagged docs page. * feat(api): SHOC design-system theme for /docs (vendored fonts) Themes the Redoc page with the canonical SHOC token set: Montserrat 600 headings / DM Sans body / JetBrains Mono code, primary #1c75bc, navy #262262 sidebar text + right panel, #f9fafb background, 244px sidebar. sortRequiredPropsFirst on; 200 responses pre-expanded. Fonts ship as lambdas/api/fonts.css (latin woff2 subsets from @fontsource 5.3.0, embedded as data URIs, ~90KB) and inline via a new __FONTS_CSS__ placeholder with the same </style breakout guard -- the offline single-response invariant holds, nothing fetches Google Fonts (test-pinned). Bundling cp + bundle-consistency pin + spec-drift asset checks extended. Verified: headless-Chrome render (theme + fonts applied), ruff, 675 pytest, cdk synth + staged-asset check. * feat(api): SHOC gradient topbar on /docs 64px fixed header with the SHOC shell gradient token (#1b1f52 -> #1c4f8f -> #1c75bc), Sea Haven wordmark in Montserrat 600, page name right-aligned in DM Sans. Redoc's scrollYOffset: 64 keeps the sticky sidebar and anchor scrolling clear of the fixed bar. Verified via headless-Chrome render. * style(api): normalize /docs header and right-panel blues The right panel's #262262 is a purple-leaning navy that clashed with the cyan-leaning gradient, and the bar's brightest point sat directly over the dark panel. Right panel now uses #1b1f52 (the gradient's own dark endpoint) and the gradient runs bright-to-dark so its dark end lands flush on the panel -- no seam, one blue family. Verified via headless-Chrome render. * style(api): right-panel gradient on /docs via bundle-pinned override Redoc's theme only takes solid colors (it derives shades from rightPanel.backgroundColor), so the gradient (#1b3d79 -> #1b3068 -> #1b1f52, continuing the topbar blend) rides as a CSS override on the styled-components classes of the per-section right-panel divs (.sc-iGgWBj.sc-gsFSXq + the .sc-dExYaf stub). Those names are deterministic for the vendored 2.5.3 bundle (verified across loads) but change on any Redoc bump: re-derive via headless probe (find elements whose computed background equals the rightPanel color). If they stop matching, the panel falls back to the solid #1b1f52 theme color -- cosmetic only. Verified via headless-Chrome render. * feat(api): collapsible samples column on /docs Redoc CE has no built-in panel toggle, so the topbar gains a Hide/Show samples button that flips .samples-collapsed on <html>: the right-panel divs hide (same bundle-pinned styled-components classes as the gradient override) and each section's content half takes the full width. Choice persists in localStorage; aria-pressed tracks state. If the pinned classes stop matching after a Redoc bump the toggle goes inert -- cosmetic only. Both states verified via headless-Chrome render. * ci(api): spec-lint CI gate + npm Dependabot coverage New spec-lint job mirrors the local npm run lint:api so openapi.json cannot drift from redocly.yaml with green CI. Dependabot gains the npm ecosystem (package.json is new; nothing watched @redocly/cli). * feat(api): docs finishing touches - x-tagGroups, favicon, docs:preview x-tagGroups sections the Redoc sidebar (Read API / Meta / SHOC Feed); inline data-URI SVG favicon (SHOC blue) stops the browser's follow-up /favicon.ico request 403ing at the gateway; npm run docs:preview wraps the real-handler local render (scripts/preview_docs.py); README gains a docs-page architecture section covering the inline pattern, theme, pinned-selector caveat, and tooling. Lint 0 errors, 675 pytest, headless render verified. --------- Signed-off-by: Adam Moussa <adam@seahavenind.com>
2026-07-24 14:04:30 -04:00
fontFamily: '"Montserrat", system-ui, -apple-system, "Segoe UI", Helvetica, Arial, sans-serif',
fontWeight: "600"
},
code: {
fontFamily: '"JetBrains Mono", ui-monospace, SFMono-Regular, Menlo, monospace'
}
feat(api): Redocly lint gate + SHOC-themed /docs (Redoc theming, topbar, collapsible samples) (#130) * feat(api): Add @redocly/cli as a dev dependency Signed-off-by: Adam Moussa <adam@seahavenind.com> * feat(api): Add Redocly configuration file with custom rules Signed-off-by: Adam Moussa <adam@seahavenind.com> * chore(api): Redocly lint config + bring openapi.json into compliance redocly.yaml from the Redocly guidelines builder, with three generated rules corrected: response-contains-property had the status codes as the required body fields (intent was the Error schema's top-level 'error'; 403 exempt since API Gateway emits AWS's {message} shape, 501 not 503); operation-4xx-problem-details-rfc7807 off (adopting RFC 7807 would be a runtime + SHOC-contract change, decided against); the two inert casing rules (parameter names, schema properties) removed because both name sets are contract-pinned (gateway resource paths, DynamoDB items). Spec changes, no runtime impact: operationIds renamed to method-prefixed kebab-case (get-work-orders, post-work-order-comment, ...); tags added to all 15 operations + root tags object (groups the Redoc sidebar); examples on all six parameters; license field; server description punctuation; two descriptions reworded to start capitalized. Real linter catches fixed: the two x-planned ops were missing their {workOrderId} path parameter and any 4xx response (403 added - true today, gateway rejects unsigned). .redocly.lint-ignore.yaml pins the six deliberate exceptions: webhook keys are the shipped SHOC contract event names (not renameable), and the x-planned ops answer only 501 (no 2xx to document). package.json: npm run lint:api. Verified: lint 0 errors, 675 pytest, headless-Chrome render of the tagged docs page. * feat(api): SHOC design-system theme for /docs (vendored fonts) Themes the Redoc page with the canonical SHOC token set: Montserrat 600 headings / DM Sans body / JetBrains Mono code, primary #1c75bc, navy #262262 sidebar text + right panel, #f9fafb background, 244px sidebar. sortRequiredPropsFirst on; 200 responses pre-expanded. Fonts ship as lambdas/api/fonts.css (latin woff2 subsets from @fontsource 5.3.0, embedded as data URIs, ~90KB) and inline via a new __FONTS_CSS__ placeholder with the same </style breakout guard -- the offline single-response invariant holds, nothing fetches Google Fonts (test-pinned). Bundling cp + bundle-consistency pin + spec-drift asset checks extended. Verified: headless-Chrome render (theme + fonts applied), ruff, 675 pytest, cdk synth + staged-asset check. * feat(api): SHOC gradient topbar on /docs 64px fixed header with the SHOC shell gradient token (#1b1f52 -> #1c4f8f -> #1c75bc), Sea Haven wordmark in Montserrat 600, page name right-aligned in DM Sans. Redoc's scrollYOffset: 64 keeps the sticky sidebar and anchor scrolling clear of the fixed bar. Verified via headless-Chrome render. * style(api): normalize /docs header and right-panel blues The right panel's #262262 is a purple-leaning navy that clashed with the cyan-leaning gradient, and the bar's brightest point sat directly over the dark panel. Right panel now uses #1b1f52 (the gradient's own dark endpoint) and the gradient runs bright-to-dark so its dark end lands flush on the panel -- no seam, one blue family. Verified via headless-Chrome render. * style(api): right-panel gradient on /docs via bundle-pinned override Redoc's theme only takes solid colors (it derives shades from rightPanel.backgroundColor), so the gradient (#1b3d79 -> #1b3068 -> #1b1f52, continuing the topbar blend) rides as a CSS override on the styled-components classes of the per-section right-panel divs (.sc-iGgWBj.sc-gsFSXq + the .sc-dExYaf stub). Those names are deterministic for the vendored 2.5.3 bundle (verified across loads) but change on any Redoc bump: re-derive via headless probe (find elements whose computed background equals the rightPanel color). If they stop matching, the panel falls back to the solid #1b1f52 theme color -- cosmetic only. Verified via headless-Chrome render. * feat(api): collapsible samples column on /docs Redoc CE has no built-in panel toggle, so the topbar gains a Hide/Show samples button that flips .samples-collapsed on <html>: the right-panel divs hide (same bundle-pinned styled-components classes as the gradient override) and each section's content half takes the full width. Choice persists in localStorage; aria-pressed tracks state. If the pinned classes stop matching after a Redoc bump the toggle goes inert -- cosmetic only. Both states verified via headless-Chrome render. * ci(api): spec-lint CI gate + npm Dependabot coverage New spec-lint job mirrors the local npm run lint:api so openapi.json cannot drift from redocly.yaml with green CI. Dependabot gains the npm ecosystem (package.json is new; nothing watched @redocly/cli). * feat(api): docs finishing touches - x-tagGroups, favicon, docs:preview x-tagGroups sections the Redoc sidebar (Read API / Meta / SHOC Feed); inline data-URI SVG favicon (SHOC blue) stops the browser's follow-up /favicon.ico request 403ing at the gateway; npm run docs:preview wraps the real-handler local render (scripts/preview_docs.py); README gains a docs-page architecture section covering the inline pattern, theme, pinned-selector caveat, and tooling. Lint 0 errors, 675 pytest, headless render verified. --------- Signed-off-by: Adam Moussa <adam@seahavenind.com>
2026-07-24 14:04:30 -04:00
},
sidebar: {
width: "244px",
backgroundColor: "#f9fafb",
textColor: "#262262"
},
rightPanel: {
backgroundColor: "#1b1f52"
},
fab: {
backgroundColor: "#1c75bc"
}
}
},
document.getElementById("redoc")
);
feat(api): Redocly lint gate + SHOC-themed /docs (Redoc theming, topbar, collapsible samples) (#130) * feat(api): Add @redocly/cli as a dev dependency Signed-off-by: Adam Moussa <adam@seahavenind.com> * feat(api): Add Redocly configuration file with custom rules Signed-off-by: Adam Moussa <adam@seahavenind.com> * chore(api): Redocly lint config + bring openapi.json into compliance redocly.yaml from the Redocly guidelines builder, with three generated rules corrected: response-contains-property had the status codes as the required body fields (intent was the Error schema's top-level 'error'; 403 exempt since API Gateway emits AWS's {message} shape, 501 not 503); operation-4xx-problem-details-rfc7807 off (adopting RFC 7807 would be a runtime + SHOC-contract change, decided against); the two inert casing rules (parameter names, schema properties) removed because both name sets are contract-pinned (gateway resource paths, DynamoDB items). Spec changes, no runtime impact: operationIds renamed to method-prefixed kebab-case (get-work-orders, post-work-order-comment, ...); tags added to all 15 operations + root tags object (groups the Redoc sidebar); examples on all six parameters; license field; server description punctuation; two descriptions reworded to start capitalized. Real linter catches fixed: the two x-planned ops were missing their {workOrderId} path parameter and any 4xx response (403 added - true today, gateway rejects unsigned). .redocly.lint-ignore.yaml pins the six deliberate exceptions: webhook keys are the shipped SHOC contract event names (not renameable), and the x-planned ops answer only 501 (no 2xx to document). package.json: npm run lint:api. Verified: lint 0 errors, 675 pytest, headless-Chrome render of the tagged docs page. * feat(api): SHOC design-system theme for /docs (vendored fonts) Themes the Redoc page with the canonical SHOC token set: Montserrat 600 headings / DM Sans body / JetBrains Mono code, primary #1c75bc, navy #262262 sidebar text + right panel, #f9fafb background, 244px sidebar. sortRequiredPropsFirst on; 200 responses pre-expanded. Fonts ship as lambdas/api/fonts.css (latin woff2 subsets from @fontsource 5.3.0, embedded as data URIs, ~90KB) and inline via a new __FONTS_CSS__ placeholder with the same </style breakout guard -- the offline single-response invariant holds, nothing fetches Google Fonts (test-pinned). Bundling cp + bundle-consistency pin + spec-drift asset checks extended. Verified: headless-Chrome render (theme + fonts applied), ruff, 675 pytest, cdk synth + staged-asset check. * feat(api): SHOC gradient topbar on /docs 64px fixed header with the SHOC shell gradient token (#1b1f52 -> #1c4f8f -> #1c75bc), Sea Haven wordmark in Montserrat 600, page name right-aligned in DM Sans. Redoc's scrollYOffset: 64 keeps the sticky sidebar and anchor scrolling clear of the fixed bar. Verified via headless-Chrome render. * style(api): normalize /docs header and right-panel blues The right panel's #262262 is a purple-leaning navy that clashed with the cyan-leaning gradient, and the bar's brightest point sat directly over the dark panel. Right panel now uses #1b1f52 (the gradient's own dark endpoint) and the gradient runs bright-to-dark so its dark end lands flush on the panel -- no seam, one blue family. Verified via headless-Chrome render. * style(api): right-panel gradient on /docs via bundle-pinned override Redoc's theme only takes solid colors (it derives shades from rightPanel.backgroundColor), so the gradient (#1b3d79 -> #1b3068 -> #1b1f52, continuing the topbar blend) rides as a CSS override on the styled-components classes of the per-section right-panel divs (.sc-iGgWBj.sc-gsFSXq + the .sc-dExYaf stub). Those names are deterministic for the vendored 2.5.3 bundle (verified across loads) but change on any Redoc bump: re-derive via headless probe (find elements whose computed background equals the rightPanel color). If they stop matching, the panel falls back to the solid #1b1f52 theme color -- cosmetic only. Verified via headless-Chrome render. * feat(api): collapsible samples column on /docs Redoc CE has no built-in panel toggle, so the topbar gains a Hide/Show samples button that flips .samples-collapsed on <html>: the right-panel divs hide (same bundle-pinned styled-components classes as the gradient override) and each section's content half takes the full width. Choice persists in localStorage; aria-pressed tracks state. If the pinned classes stop matching after a Redoc bump the toggle goes inert -- cosmetic only. Both states verified via headless-Chrome render. * ci(api): spec-lint CI gate + npm Dependabot coverage New spec-lint job mirrors the local npm run lint:api so openapi.json cannot drift from redocly.yaml with green CI. Dependabot gains the npm ecosystem (package.json is new; nothing watched @redocly/cli). * feat(api): docs finishing touches - x-tagGroups, favicon, docs:preview x-tagGroups sections the Redoc sidebar (Read API / Meta / SHOC Feed); inline data-URI SVG favicon (SHOC blue) stops the browser's follow-up /favicon.ico request 403ing at the gateway; npm run docs:preview wraps the real-handler local render (scripts/preview_docs.py); README gains a docs-page architecture section covering the inline pattern, theme, pinned-selector caveat, and tooling. Lint 0 errors, 675 pytest, headless render verified. --------- Signed-off-by: Adam Moussa <adam@seahavenind.com>
2026-07-24 14:04:30 -04:00
(function () {
var KEY = "procurement-docs-samples-collapsed";
var btn = document.getElementById("samples-toggle");
function apply(collapsed) {
document.documentElement.classList.toggle("samples-collapsed", collapsed);
btn.textContent = collapsed ? "Show samples" : "Hide samples";
btn.setAttribute("aria-pressed", String(collapsed));
}
var initial = false;
try { initial = localStorage.getItem(KEY) === "1"; } catch (e) {}
apply(initial);
btn.addEventListener("click", function () {
var collapsed = !document.documentElement.classList.contains("samples-collapsed");
try { localStorage.setItem(KEY, collapsed ? "1" : "0"); } catch (e) {}
apply(collapsed);
});
})();
feat(api): procurement-api read stack + OpenAPI docs (SHOC reconciliation path) (#127) * feat(api): add procurement-api stack - read API + OpenAPI docs page Third CDK stack: API Gateway REST API (IAM SigV4) over both pipelines' tables, replacing SHOC's retired SyncController cross-account DynamoDB scan as the reconciliation/backfill path. - lambdas/api/: handler (healthcheck + docs-token gate + router dispatch), router (single route table), pagination (opaque cursor, hostile -> 400), Decimal-safe serialization, wo_repo/po_repo reads. No VendorReplies. - OpenAPI 3.1 spec as source of truth incl. top-level webhooks section documenting the outbound SHOC feed; phase-2 write endpoints x-planned (router answers 501). Self-contained /docs page, no CDN. - Auth: AWS_IAM on data routes + resource policy scoped to exactly arn:aws:iam::396287094661:role/shoc-backend-dev on GET/*; /docs and /openapi.json carve-out is token-gated in the Lambda via shared web_ui_auth (fail-closed, INFRA-74 posture). - KMS: explicit Decrypt/DescribeKey on the DynamoDB CMK from SSM (name-imported table drops the key association - INFRA-104 class). - Alarms: errors/throttles/duration(p99>=22.5s) + gateway 5xx, ALARM-only to site-alerts. No access logging in v1 (docs ?token= shim stays out of logs); cloud_watch_role=False. - Tests: handler auth-seam + routing + Decimal round-trip; moto cursor pagination incl. hostile cursors; spec<->router drift gate; bundle AST pins for the api command; pytest.ini --cov + loader siblings. - Deploy role: third stack DescribeStacks ARN + procurement-api smoke invoke ARN (re-run create-deploy-role.sh before merge). * harden(api): apply sh-security-review findings to procurement-api Fan-out (6 detectors) + review findings resolved: Correctness / DoS: - pagination: require EXACT key-set match (was subset) so a partial/foreign composite cursor can't reach DynamoDB as an inconsistent ExclusiveStartKey -> ValidationException -> 500; comments Query now pins the cursor's work_order_id to the path entity. - handler: map botocore ValidationException to 400 (defense in depth) so a crafted cursor can't drive the zero-threshold 5xx alarm. - web_ui_auth: compare tokens as bytes; a non-ASCII presented token now fails closed (401) instead of crashing hmac.compare_digest into a 500. Resolves the pre-existing xfail(strict) follow-up test; hardens the web UIs too. Docs page: - typeStr() now escapes the one spec-derived string that reached innerHTML. - spec inlined into the docs <script> block escapes "<" -> < (</script> breakout guard); /openapi.json still served byte-faithful. - Cache-Control: no-store + Referrer-Policy: no-referrer on docs responses so the ?token= URL stays out of caches/Referer. - spec-drift test asserts the committed spec carries no "</" / "<!--". IAM / IaC: - resource policy enumerates the 7 data GET resources instead of GET/* so a future GET route can't silently inherit SHOC cross-account reach. - kms:Decrypt grant gains a kms:ViaService=dynamodb condition. - stage throttling (50 rps / 100 burst) bounds the unauthenticated /docs blast radius below the 10k account default. - corrected the PATCH/POST comment (same-account callers aren't blocked by the resource policy; 501 handler + absent write grant are the gate). - documented the RETAIN log-group first-deploy rollback trap and the resource-policy-needs-redeploy gotcha in-stack. Mandatory GPT-4.1 cross-family review of the full policy surface: no BLOCK/FIX. 675 tests pass, ruff clean, cdk synth green.
2026-07-23 19:32:20 -04:00
</script>
</body>
</html>