feat(state-mappings): DELETE empties caps; POST/PATCH require non-empty + auto-add can_view#2705
feat(state-mappings): DELETE empties caps; POST/PATCH require non-empty + auto-add can_view#2705ravverma wants to merge 5 commits into
Conversation
… non-empty + auto-add can_view - New DELETE /state/access-mappings/:id — empties the row's granted_capabilities ([]), leaving the row active but granting nothing. Removal path that does NOT tombstone/soft-revoke; capabilities are re-addable via PATCH. Same manager + per-resource (flow 8.3) scoping as PATCH. Audited as update_capabilities. - POST and PATCH now REJECT an empty grantedCapabilities array (PATCH previously allowed empty; emptying now goes through DELETE). - POST and PATCH always persist the product baseline <product>/can_view, adding it when the request omits it (you cannot manage/configure/deploy a resource you cannot view). Wiring: route in routes/index.js, INTERNAL_ROUTES classification, facs-capabilities (<product>/can_manage_users in LLMO + ASO), OpenAPI delete op + rebuilt docs bundle. Unit + IT coverage updated. npm test: 13002 passing; docs:lint clean. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
There was a problem hiding this comment.
Hey @ravverma,
Verdict: Request changes - one blocking test gap (CI will fail) and a spec sync issue.
Complexity: HIGH - medium diff; API surface change (OpenAPI contract modified).
Changes: Adds DELETE /state/access-mappings/:id to empty capabilities, makes POST/PATCH reject empty arrays and auto-inject can_view (9 files).
Note: Recommend a human read before merge - this change modifies a shared contract (OpenAPI spec). The bot review is a complement to, not a replacement for, a human read here.
Note: CI check it-postgres is still pending - the blocking finding below predicts it will fail.
Must fix before merge
- [Critical] Integration test not updated for flow 8.3 brandManager section -
test/it/shared/tests/state-access-mappings.js:~500(details inline) - [Important] POST endpoint OpenAPI description not updated for can_view auto-injection -
docs/openapi/state-access-mappings-api.yaml(details inline)
Non-blocking (5): minor issues and suggestions
- suggestion: Document DELETE idempotency in OpenAPI spec (calling DELETE on an already-empty row returns 200 with the unchanged row) -
docs/openapi/state-access-mappings-api.yaml:~214 - suggestion: The 400 response for PATCH-with-empty-array should mention "use DELETE /state/access-mappings/:id to revoke all capabilities" so callers can self-service the migration -
src/controllers/state-access-mappings.js:~797 - nit: No integration test for can_view auto-injection round-trip (unit tests cover it, but an IT asserting POST with
['llmo/can_configure']stores['llmo/can_configure','llmo/can_view']would catch middleware/DB divergence) -test/it/shared/tests/state-access-mappings.js - nit:
ensureBaselineCanViewuses case-sensitiveincludes()- if validation already guarantees lowercase capability prefixes, a one-line comment documenting that assumption would prevent future confusion -src/controllers/state-access-mappings.js:~295 - nit: Audit operation for DELETE is
update_capabilitieswith empty set - consider whether a distinct operation (e.g.revoke_capabilities) would aid SOC analysts, or add a comment documenting the intentional reuse -src/controllers/state-access-mappings.js:~920
Skill: pr-review | Model: us.anthropic.claude-opus-4-6-v1[1m] | Duration: 3m 38s | Cost: $9.60 | Commit: c4897b6a65b5d4ba6679b8937e42f179b2dbd397
If this code review was useful, please react with 👍. Otherwise, react with 👎.
…elete-and-canview # Conflicts: # docs/index.html
…notFound; docs - IT flow 8.3: PATCH-empty now 400, revoke via DELETE (200) + DELETE 403 on unmanaged binding — fixes the failing it-postgres assertion - add can_view baseline auto-injection IT round-trip (POST + GET) - unit test: resource-scoped DELETE 404s when the target row is absent (covers the in-patch deleteMapping notFound branch for codecov/patch) - OpenAPI: document can_view auto-injection on POST, DELETE idempotency, and point the PATCH-empty 400 at DELETE; rebuild docs/index.html - comments: document ensureBaselineCanView case-sensitivity and the intentional update_capabilities audit reuse for DELETE Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
This PR will trigger a minor release when merged. |
|
Addressed the review + CI failure (commit 14db376): Must fix
Non-blocking
Also merged latest |
There was a problem hiding this comment.
Hey @ravverma,
Verdict: Request changes - audit events diverge from stored state after can_view injection.
Complexity: HIGH - medium diff; API surface (OpenAPI contract modified).
Changes: Adds DELETE /state/access-mappings/:id, makes POST/PATCH reject empty arrays and auto-inject can_view (9 files).
Note: Recommend a human read before merge - this change modifies a shared contract (OpenAPI spec). The bot review is a complement to, not a replacement for, a human read here.
Must fix before merge
- [Important] Audit events in createMapping/patchMapping log request payload, not the stored value with injected can_view -
src/controllers/state-access-mappings.js:~714,~839(details inline)
Non-blocking (2): minor issues and suggestions
- nit: Dead
allowEmptyparameter and stale JSDoc onvalidateGrantedCapabilities(no caller passes it after this PR) -src/controllers/state-access-mappings.js:~259 - nit: No test asserts DELETE idempotency on an already-empty row (OpenAPI documents this guarantee) -
test/controllers/state-access-mappings.test.js
Previously flagged, now resolved
- Integration test added for flow 8.3 brandManager DELETE path
- POST OpenAPI description documents can_view auto-injection
- DELETE idempotency documented in OpenAPI spec
- PATCH-empty 400 message mentions DELETE alternative
- can_view auto-injection integration test round-trip added
- Case-sensitive includes() safety comment added
- Audit operation reuse for DELETE documented
Skill: pr-review | Model: us.anthropic.claude-opus-4-6-v1[1m] | Duration: 2m 11s | Cost: $9.30 | Commit: 14db3765e36a4a3c0210e15d4e70bc38c27ac9e8
If this code review was useful, please react with 👍. Otherwise, react with 👎.
| // De-duplicate, then guarantee the baseline `<product>/can_view`: it is the | ||
| // read capability implied by any other grant, so it is always stored even | ||
| // when the request omits it. | ||
| const capabilitiesToStore = ensureBaselineCanView( |
There was a problem hiding this comment.
issue (blocking): emitAuditEvent in both createMapping and patchMapping logs grantedCapabilities (the raw request input) rather than capabilitiesToStore (the value written to the DB with the auto-injected can_view).
When a caller sends ['llmo/can_configure'], the DB stores ['llmo/can_configure', 'llmo/can_view'], but the audit trail records only ['llmo/can_configure']. For compliance and SOC analysis on an access-management system, the authoritative record should reflect what capabilities the subject actually received.
Fix: pass capabilitiesToStore to emitAuditEvent instead of grantedCapabilities, or add a separate requestedCapabilities field alongside grantedCapabilities: capabilitiesToStore to preserve both.
create/patch audit events now record `capabilitiesToStore` (incl. the auto-injected `can_view` baseline) instead of the raw `grantedCapabilities` request — the audit trail reflects what the subject actually received. Adds a unit test asserting the auto-injected can_view appears in the event. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
Addressed the blocking audit finding (commit 0e9936a):
|
There was a problem hiding this comment.
Hey @ravverma,
Verdict: Request changes - one remaining test gap for the patchMapping audit fix.
Complexity: HIGH - large diff; API surface change (OpenAPI contract modified).
Changes: Fixes audit events to log persisted capabilities (incl. auto-injected can_view) instead of raw request input (9 files).
Note: Recommend a human read before merge - this change modifies a shared contract (OpenAPI spec). The bot review is a complement to, not a replacement for, a human read here.
Note: CI check it-postgres is still pending.
Must fix before merge
- [Important] No discriminating test for patchMapping audit-event fix -
test/controllers/state-access-mappings.test.js:~725(details inline)
Non-blocking (2): minor issues and suggestions
- nit: Dead
allowEmptyparameter and stale JSDoc onvalidateGrantedCapabilities(no caller passes it after this PR) -src/controllers/state-access-mappings.js:~265 - nit: No test asserts DELETE idempotency on an already-empty row (OpenAPI documents this guarantee) -
test/controllers/state-access-mappings.test.js
Previously flagged, now resolved
- Audit events in createMapping/patchMapping now log persisted capabilities with auto-injected can_view
- Integration test for flow 8.3 brandManager DELETE path added
- POST OpenAPI description documents can_view auto-injection
- DELETE idempotency documented in OpenAPI spec
- PATCH-empty 400 message mentions DELETE alternative
- can_view auto-injection integration test round-trip added
- Case-sensitive includes() safety comment added
- Audit operation reuse for DELETE documented
Skill: pr-review | Model: us.anthropic.claude-opus-4-6-v1[1m] | Duration: 2m 57s | Cost: $4.60 | Commit: 0e9936a4aa2374bc667d7cef7a88dbaab06db6f1
If this code review was useful, please react with 👍. Otherwise, react with 👎.
| expect(res.status).to.equal(201); | ||
| const event = stubs.insertFacsAccessMappingAuditEvent.firstCall.args[1]; | ||
| // Records what the subject actually received, not the raw request. | ||
| expect(event.grantedCapabilities).to.have.members([ |
There was a problem hiding this comment.
issue (blocking): The production fix correctly changes both createMapping (line ~761) and patchMapping (line ~858) to pass capabilitiesToStore to emitAuditEvent. A new test here verifies the createMapping path by sending ['llmo/can_configure'] and asserting the audit event includes the injected can_view.
However, the existing patchMapping audit test sends ['llmo/can_view', 'llmo/can_configure'] as input - can_view is already present, so that test passes regardless of whether the code uses raw input or post-injection value. A regression on the patchMapping line would go undetected.
Fix: add a symmetric test for patchMapping that sends only ['llmo/can_configure'] and asserts the audit event includes both ['llmo/can_configure', 'llmo/can_view'] - same pattern as this test.
…wEmpty - add symmetric patchMapping audit test (sends only can_configure, asserts can_view is auto-injected into the event) — catches a regression the prior test could not - add DELETE idempotency unit test (already-empty row → 200 unchanged) - remove the dead `allowEmpty` param + stale JSDoc on validateGrantedCapabilities (no caller passes it; POST/PATCH both require non-empty, revoke is via DELETE) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
Addressed the latest review (commit 8bc6818): Must fix
Non-blocking
Local: 118 controller tests pass, lint clean. |
There was a problem hiding this comment.
Hey @ravverma,
⚠ Degraded review - no spec document was found for this change (searched the PR links, the touched repos' docs, the architecture/guidelines docs, and linked Jira). This review covers code-level quality but could not validate the change against an agreed design, so confidence is reduced. Add a spec link (PR template section 4) and re-request review for a full-confidence pass.
Verdict: Approve - all prior findings addressed, no new issues found.
Complexity: HIGH - medium diff; API surface change (OpenAPI contract modified).
Changes: Adds DELETE /state/access-mappings/:id to empty capabilities, makes POST/PATCH reject empty arrays and auto-inject can_view (9 files).
Note: Recommend a human read before merge - this change modifies a shared contract (OpenAPI spec). The bot review is a complement to, not a replacement for, a human read here.
Non-blocking (0): no minor issues or suggestions
No findings.
Previously flagged, now resolved
- Discriminating patchMapping audit test added (sends only can_configure, asserts can_view in event)
- Dead
allowEmptyparameter and stale JSDoc removed fromvalidateGrantedCapabilities - DELETE idempotency unit test added (already-empty row returns 200 unchanged)
Skill: pr-review | Model: us.anthropic.claude-opus-4-6-v1[1m] | Duration: 1m 1s | Cost: $5.09 | Commit: 8bc6818f1b2930bb90aa62cfb0dbeb385000ba6b
If this code review was useful, please react with 👍. Otherwise, react with 👎.
What
Three changes to the state-layer access-mapping endpoints, per request:
DELETE /state/access-mappings/:id— empties the row'sgrantedCapabilities([]), leaving the binding active but granting nothing. This is the removal path. It does not tombstone/soft-revoke (revoke is left out for now); capabilities can be re-added viaPATCH. Same manager + per-resource (flow 8.3) scoping as PATCH; audited asupdate_capabilitieswith an empty set.grantedCapabilities. PATCH previously allowed empty (that was the old "empty = remove access"); emptying now goes through DELETE.<product>/can_view— the baseline read capability implied by any grant is always persisted, even when the request omits it (e.g.['llmo/can_configure']is stored as['llmo/can_configure','llmo/can_view']).Wiring
routes/index.js, classified inrequired-capabilities.jsINTERNAL_ROUTES, and mapped to<product>/can_manage_usersinfacs-capabilities.js(LLMO + ASO).deleteop onstate-access-mapping-by-id+ corrected PATCH description;docs/index.htmlrebuilt.Testing
npm test→ 13002 passing;npm run docs:lint→ 0 errors. Lint clean.🤖 Generated with Claude Code