Skip to content

feat: add entitlements create - #112

Merged
leet-c1 merged 10 commits into
mainfrom
feat/entitlements-create
Sep 3, 2026
Merged

feat: add entitlements create#112
leet-c1 merged 10 commits into
mainfrom
feat/entitlements-create

Conversation

@leet-c1

@leet-c1 leet-c1 commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

c1i entitlements create wraps the three-call sequence — resource type,
resource, entitlement — into one command. The guide already documented the raw
sequence; this makes it one call, and --owner-id rides along in the create
request so owners need no follow-up.

c1i entitlements create --app-id "$APP_ID" --display-name "Payroll viewer" \
  --resource-type ROLE --resource-type-display-name "Payroll role"

# reuse objects that already exist
c1i entitlements create --app-id "$APP_ID" --display-name "Payroll admin" \
  --resource-type-id "$RT_ID" --resource-id "$RES_ID"

Behaviour worth knowing

  • Not atomic, and nothing is rolled back. If a later write fails, the
    objects the earlier ones created still exist. The error names them, the flags
    that reuse them, and the flags the retry must drop — following it verbatim is
    what makes the retry succeed, since a flag describing an object that now
    exists is refused.
  • Only CUSTOM resource-type kinds repeat per app. A second ROLE, VAULT
    or PROFILE_TYPE returns a 500, app resource type already exists, which
    maps to exit 6 even though retrying never helps. Reuse the existing one with
    --resource-type-id.
  • Empty values fail before any request. An unset shell variable in
    --owner-id used to create an entitlement with no owners at exit 0 —
    undetectable by reading back, since owner writes are async.
  • --duration-grant takes a protobuf duration: 3600s, not 1h.

Verification

Every flag and failure path exercised live, exit codes captured directly rather
than through a pipe. The ownerless-create defect reproduced before the fix and
confirmed after. The remediation loop run end to end: the original invocation
fails, the retry it prints succeeds. The reuse advice in all four docs executed
— dropping both refused flags exits 0, keeping either exits 2. Owner visibility
polled rather than read once. Test objects soft-deleted, verified by
deletedAt.

--owner-id uses the shared repeatable-flag registrar from #111 rather than a
private copy of the same rule.

🤖 Generated with Claude Code

leet-c1 and others added 10 commits September 1, 2026 04:22
Collapses the resource-type/resource/entitlement sequence the
configure-new-app guide previously spelled out as three raw "c1i api"
calls into one command.

--resource-type-id and --resource-id skip whichever of the first two
POSTs the caller already has, so one resource type can carry many
resources and one resource many entitlements; without them all three
are sent. The server requires appResourceTypeId AND appResourceId on
the entitlement even though the OpenAPI schema marks only displayName
required, so --resource-id without --resource-type-id is a usage error
(exit 2) rather than a 400.

--duration-grant covers the standing-vs-time-boxed distinction inline
rather than sending readers to a raw POST for it. It takes a protobuf
duration ("3600s"); a Go-style "1h" is refused by the server.

The three writes are not atomic and nothing is rolled back, so a
failure part-way through names the objects this run created and the
flags that reuse them. --dry-run previews all three requests, standing
in NEW_APP_RESOURCE_TYPE_ID/NEW_APP_RESOURCE_ID for ids that only exist
after a real preceding step, and its banner names only the stand-ins
the preview actually uses.

The resource-type enum has one definition, resourceTypeKinds, that the
help text and flag usage are built from and that a test holds README.md
and the guide to.

Also corrects the guide, which opened by saying no such command exists,
and two of its timing claims that this session measured: entitlement
owners lag the write like app owners do (116s), and the entitlement
search index is fast but not transactional.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
… advice work

--owner-id "" (an unset shell variable) created an ownerless entitlement at
exit 0, which an async owner read cannot tell apart from "not provisioned
yet". Empty and whitespace-only owner ids are now a usage error before
anything is sent; the flag is a StringArray because pflag's slice parser drops
an empty value outright, so `--owner-id "" --owner-id U` never reached the
check. An explicit --resource-type-display-name/--resource-display-name ""
likewise fell back to --display-name and mis-named the object; both now take
the same requireNonEmptyIfSet guard as the id flags.

The partial-failure message advised a retry the command then rejected: the
reused ids make the create-only flags of the original invocation a usage
error. It now names those flags for dropping, and a test parses the message
and feeds the retry back through the flag parser.

Help text: only a CUSTOM resource type can repeat on one app -- a second ROLE,
GROUP or VAULT 500s with "app resource type already exists", quoted so it is
greppable from both directions, and reused via --resource-type-id. The exit
code for it is unchanged. The opening sentence also claimed the app must be
manually managed; the app need not be, only the entitlement created on it is.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The partial-failure message and the CUSTOM-singleton caveat had landed in
the command help, README and agents.md but not in docs guide
configure-new-app, which was rewritten in this same branch. The guide quoted
the pre-fix retry — one its own example command cannot produce, and the one
the fix exists to stop advising — and listed all eight resource-type kinds
with no hint that only CUSTOM may repeat.

Two guards so neither drifts again: the singleton check now covers the
guide, and a new test builds the remediation from createdSoFar and requires
the docs that quote it to carry the flags-to-drop clause.

The remediation test also covers the resource-create failure, where the
retry must keep --resource-display-name because the object it names was
never created. That path fails silently rather than loudly, and nothing
pinned it.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The verbatim test built the expected message and then asserted only that the
docs contain the word "dropping", so renaming a flag left both docs quoting a
message the code can no longer emit while the test stayed green. It now
compares against the message itself, built by the real planner rather than a
hand-made plan, so a regression in how the drop list is populated fails here.

The singleton paragraph added in the previous commit told the reader to reuse
the existing type with --resource-type-id without saying to drop
--resource-type, so following it exits 2 — the same class of guide-advised
retry that commit removed from this file.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The previous commit fixed half of a two-flag rejection: reusing a type also
refuses --resource-type-display-name, which the guide's own worked example
passes, so following the corrected paragraph verbatim still exited 2. The
other three docs carried the same incomplete advice, and the agent doc stated
no exclusivity at all.

The refused flags are now one list in the code, and a test holds every doc
that advises the reuse to it — so adding a third flag fails until the docs
say so. Prose is not a quote, which is why the verbatim guard could not see
this.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The previous guard asserted almost nothing. "--resource-type" is a prefix of
"--resource-type-id", so that half was satisfied by the id every doc already
names; the other half was satisfied by unrelated text elsewhere in three of
the four docs, one of it an example that PASSES the flag. It would not have
caught the defect it was written for.

Three heuristic versions failed the same way — matching prose, then anchoring
on the instruction, which let a doc that dropped the instruction be skipped
rather than failed. Each doc now carries a fixed clause per reuse pair,
required verbatim, and the clause is checked against the code's own list so
the docs cannot be complete against a stale contract.

The retry message was still built from a hardcoded copy of the flags, so
adding one would have left the message advising a retry that exits 2 while
the docs looked correct. Both pairs are single lists now, read by the reject
call, the message and the docs alike.

--resource-id refuses --resource-display-name and no doc said so; all four now
do, verified live.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The check that the clause names every refused flag used a substring match, so
"--resource-type" was satisfied by "--resource-type-display-name" in the same
clause — the exact prefix bug the clause design was meant to close, one layer
in. A reviewer shortened the clause to name one flag, the whole suite stayed
green, and the original defect came back: advice that exits 2.

docMentionsFlag already exists for this, with a comment describing the trap.
Using it, rather than a fourth hand-rolled matcher.

Requiring the clause without backticks forced bare flag names into two
markdown files that backtick everything else, and restoring correct markdown
failed the build. Backticks are stripped before comparing instead.

Also fails a doc that carries the clause but never names the id flag, and one
that negates it. Renamed: it covers resources too.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
flattenDoc was a second name for flatten, which this package already had.
Deleted. The four docs that carry this command's claims were written out in
full by two guards; extracted once. The other two guards keep their own sets
deliberately -- the kinds check also reads the flag's usage string, and the
remediation quote lives in only two docs.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This branch carried its own copy of the empty-occurrence rule, written before
the shared one existed. Two implementations of one rule is the drift #111 was
opened to remove, and its guard rejected the direct StringArray registration
here, so keeping the copy would have failed CI on main.

The bespoke wording is gone with it; the three rows that asserted it now
assert the shared one, which TestRepeatableStringFlagErrorHasOneWording pins.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@leet-c1
leet-c1 merged commit 44ac0a5 into main Sep 3, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant