Skip to main content

Defining a feature

Vagaris answers two different questions about a feature, and it is worth knowing which one you are asking. Convergence answers "what is true of this feature, and what work closes the difference" — it needs a target to measure against. Definition answers "should this exist at all, for whom, and what would good look like" — it is how a target comes to exist in the first place.

If you ask a feature with no target what its gaps are, the honest answer is not a list of gaps. It is the target is not defined. These commands are how you fix that.

The path​

vagaris feature propose            # a signal becomes a candidate
vagaris feature definition write # what it is for, who it serves, what it must do
vagaris feature prior-art record # what already exists — cited, not guessed
vagaris feature review record # the org's seats propose targets on the dimensions they own
vagaris feature decide # a human ratifies, defers, rejects or returns it
vagaris feature promote # a ratified feature becomes an initiative
vagaris feature commission # and only then does work get created

Each step is separately useful. You can propose something today, define it next week, and let the reviews accumulate until there is enough to decide on.

Propose​

A proposal is a feature node with a signal attached. The signal carries a citation — a customer conversation, a support pattern, an incident, a competitor capability, a probe result. A proposal without one is an opinion, and the system will hold it as such.

vagaris feature propose --slug agent-versioning \
--name "Draft, publish and roll back an agent" \
--product vagary-voice \
--signal-kind roadmap \
--signal-summary "Changing an agent today is destructive: no draft, no diff, no way back" \
--signal-citation "operator direction 2026-09-03"

This creates a normal feature. There is no separate "candidate" object to promote later — the feature's lifecycle is derived from what exists about it, so the thing you bookmark today is the thing that is still there after it ships.

Define​

The definition is an immutable version. Writing again appends a new one; nothing is edited in place, so a decision always points at exactly the text it was made against.

vagaris feature definition write agent-versioning --content-file ./definition.json
vagaris feature definition get agent-versioning

Sections you have not written are not defined rather than empty — the difference matters, because "nobody has answered this yet" and "there is nothing to answer" are opposite facts and the system will not conflate them for you.

Prior art, before you decide to build​

vagaris feature prior-art list agent-versioning
vagaris feature prior-art record agent-versioning \
--kind internal_code \
--name "cognitive_program_versions" \
--citation "packages/db/src/schema/cognitive_program_lifecycle.ts:48" \
--discharges-dimension 11 \
--recommendation UPGRADE \
--rationale "Draft/published pointers already enforced by a partial unique index"

Search before you record. The search runs over the organization's own estate, and its result is what a recorded candidate cites:

vagaris feature prior-art search agent-versioning --q "version" --limit 20

A candidate must name a citation and say which dimension or claim it discharges. A link with nothing behind it is not evidence, and the command will refuse it. If a search finds nothing, say what you searched — an empty list and "nobody looked" are indistinguishable otherwise.

Review​

A review is filed by one of the organization's registered seats, and its output is structured targets, not prose. This is how a new feature's dimensions get populated: by review, never by an archetype guessing on your behalf.

vagaris feature review record agent-versioning \
--definition-version <version-uuid> --agent <seat-uuid> \
--lens architecture --verdict needs_evidence \
--targets-file ./targets.json
vagaris feature review list agent-versioning

Two seats may disagree about the same dimension, and both proposals survive. The contest is visible at the decision rather than silently resolved by whoever wrote last.

A proposal nobody contests can be lifted onto the target row the organization holds, so a defined but undecided feature shows a target where the end-state read looks:

vagaris feature promote-proposal agent-versioning --dimension 11 --target TENANT_ZERO_PROVEN

This confers no authority. The row derives as proposed and Commission still refuses it. A contested dimension is refused here outright, because choosing between two reviews is what ratification is.

The packet a decision is made on​

vagaris feature definition packet agent-versioning --version <version-uuid>

The packet is rendered for a named version, never "the current one". It carries the content, every review, the dissent named separately, the alternatives, and a content hash. A decision cites that hash, so the record can always say which packet the human actually had in front of them. If a review lands after the packet was read, the hash changes and the decision says so.

Decide — the one step that is not automatable​

Before deciding, you can ask the door what it will say to you:

vagaris feature definition authority agent-versioning

It answers per outcome. A ratify-family outcome needs the grant; return, defer and reject do not, so a reviewer without ratification authority can still send a package back.

vagaris feature decide agent-versioning --kind RATIFY \
--target TENANT_ZERO_PROVEN --ratified-dimensions 3,11,19 \
--rationale "Draft/publish/rollback is the smallest safe change unit for a live call flow"

Ratification requires a human principal holding product:ratify_feature for that organization and product. Agent seats propose, review, and may record NEEDS_EVIDENCE, DEFER or REJECT — they may not ratify. Being a member of an organization is not the same as being empowered to decide for it.

It also requires an interactive human session, and — where the credential can prove it — a recently authenticated one. Holding a valid session is not the same as being present for the decision: a live cookie on an unattended laptop would otherwise produce a record asserting that a named person decided.

The control has three arms, and they do not all reach every credential yet. Say which one is holding before relying on it:

ArmWhat it bindsReaches
Credential classa board key, a cloud-tenant token and an agent credential are refused outright (ratification_requires_interactive_session) — not a step-up: re-authenticating a board key does not make it a personevery path
Authentication agea session or federated credential authenticated longer ago than the window (ten minutes by default; VAGARIS_RATIFICATION_REAUTH_MAX_AGE_SECONDS) is refused with step_up_required (details.reason: ratification_requires_recent_authentication, details.step_up: stale_authentication)browser sessions and federated identity — the issuer stamps auth_time on the token; a token carrying none is refused, not waved through
Multi-factor assertiona ratification without the issuer's second-factor acr (IDENTITY_MFA_ACR) is refused with step_up_required (details.reason: ratification_requires_mfa, details.step_up: second_factor_required, details.acr_required: true)federated identity (identity_oidc)

The remedy for a step_up_required refusal is a step-up, not a sign-out: the same browser sign-in re-run with prompt=login and max_age=0, so the refreshed credential's auth_time is now. In the Console the decision panel shows "Additional verification required — Verify identity" and retries the same decision on return. On the CLI, feature decide and definition decide offer the step-up on the refusal and retry once — --yes runs it without asking (the only way a non-interactive run proceeds) — and auth step-up runs it on its own. Neither sends the issuer an acr_values: the fleet issuer reads none, and whether the re-authentication includes a second factor is the organization's login policy at the issuer, not a CLI option — so a second_factor_required refusal is cured only if that policy demands the factor. Because the remedy is one click, the window is ten minutes. The token and its claims are stored and used, never printed. The credential class, the authentication age and the MFA signal are written onto the decision either way, so "which ratifications were taken with no factor asserted" is a query rather than an inference from silence. The gate is proven through the action by governance verify-step-up (POST …/governance/step-up-policy/verify; requires product:ratify_feature or instance admin): board key, stale MFA session, single factor, wrong organization, wrong authority → each denied by its own name, and each row names WHICH check it binds (the credential-class arm, the age arm, the MFA arm, the organization boundary in the route, the grant gate in decide()); fresh MFA session → allowed; one evidence record ratification.step_up_policy on the organization. The record also says what the property does NOT bind: its actors are injected past bearer verification (token_signature_verified: false), so a PASS proves the gate's predicates and not the issuer's signature — that leg is proven by the consumer-boundary integration test, on signed tokens.

The freshness requirement binds the acts that confer standing — the ratify family and ADOPT_EXTERNAL — and deliberately not REJECT, DEFER or RETURN_FOR_REVISION. Making a reviewer re-authenticate in order to say "not yet" would raise the bar for restraint to the bar for authority, and a gate whose "send it back" costs more than its "yes" is a gate that pushes people towards yes.

What a ratification grant may bound​

The grant's scope is a closed vocabulary: a key outside it is refused when the grant is written and again when it is read, because a bound nobody enforces reads as a limit and behaves as none.

KeyMeaning
product (or products, or allow: ["product:<slug>"])The products this authority reaches. Required — a grant naming none authorizes nothing, and ["*"] spells org-wide breadth deliberately.
featureNarrows the authority to named features (slugs or feature:<slug> node ids). Absent means every feature in the covered products.
max_target_maturityThe highest tier it may ratify at (TENANT_ZERO_PROVEN · PAID_PRODUCTION · ENTERPRISE). A decision above the ceiling is refused.
delegableWhether the authority is meant to be passed on. Defaults to false and is recorded on every decision it authorizes.
reason · retires_whenWhy the breadth is held, and the deterministic revoke trigger — the named condition that ends the authority (a role change, a replacement, a departure, a scope transfer). retires_when is required on every ratify grant scope; reason is required for an org-wide (*) one.
review_byThe ISO-8601 date by which someone undertakes to look again. Required on every ratify grant scope. A review date, not an expiry — past it the grant keeps authorizing and every surface (the decision record, the authority preview, the Console gate) reports the review as overdue. What ends an authority is retires_when or an ordinary revoke: somebody's decision, never a clock's.

Whatever bounds the grant carries are snapshotted onto the decision as they were read at decision time, so revoking or widening the grant afterwards cannot re-describe the authority a past decision was taken under.

The outcomes are RATIFY, RATIFY_AS_EXPERIMENT, RATIFY_FOR_LATER_MATURITY, DEFER (with a trigger), REJECT, and RETURN_FOR_REVISION. The last one is what makes this a review rather than a yes/no.

Ratifying is not deployment approval. It makes the feature eligible for roadmap work. Deploying, migrating, changing pricing or communicating with customers keep their own gates.

Delegating ratification authority​

product:ratify_feature is a grant like any other — an owner confers it on a named member with vagaris access grant, scoped to the one product that member may decide for:

vagaris access members --company-id acme-co
vagaris access grant <memberId> --company-id acme-co \
--permission product:ratify_feature \
--products voice \
--reason "covering ratification while the voice product owner is on the FY review" \
--retires-when "the voice product owner returns from the FY review" \
--review-by 2026-12-01

One product per grant. A decision-class grant naming more than one product is refused at the write door with decision_grant_names_multiple_products. Authority over two products is two conferrals, so that either can be reviewed, narrowed or revoked without touching the other, and so the audit record names which product's authority was exercised. A single grant listing both makes "revoke their voice authority" an edit of the same object carrying their ledger authority, which is how scopes silently widen.

Read a member's grants back in full — scope, who conferred each, and the state of any bootstrap exception (below):

vagaris access grants <memberId> --company-id acme-co

One human in the organization: the single-human bootstrap exception. Separation of duties wants the ratifier to be neither the instance root nor the company owner, and with one real person that is unsatisfiable. A second account for the same person is refused as a separation-of-duties illusion (two credentials, one person). Instead the root identity confers product:ratify_feature on itself carrying a recorded exception — reason, the fixed retire trigger, and who recorded it (stamped by the server, never by the client):

vagaris access grant <root-memberId> --company-id acme-co \
--permission product:ratify_feature \
--products voice \
--reason "one real human in this organization until a second Product Authority is onboarded" \
--retires-when "a second eligible named human Product Authority is onboarded" \
--review-by 2026-12-01 \
--bootstrap-exception single_human

The conferral and every ratification under it require step-up — a federated sign-in with a second factor (the CLI's auth step-up verb); a Console session is refused step_up_required with cause bootstrap_exception_requires_step_up in details.reason — one envelope for one condition, so the CLI's retry-once path fires for the bootstrap principal exactly as it does for anyone else. The exception retires by itself the moment a second eligible human holds product:ratify_feature for the same product: root's next ratification is refused ratification_bootstrap_exception_retired, the scheduled re-evaluation pass records the retirement on the grant, and vagaris access grants shows it as RETIRED with who retired it. Retirement is one-way — revoking the second human's grant later does not bring the exception back.

Four acts, not one. A reviewer who may only approve is not a decision-maker, so the authority is a bundle: product:ratify_feature, product:reject_feature, product:return_for_revision and product:defer_feature. All four are decision-class, so none can be self-granted, minted by an invite, or born from a seed. Holding a restraint key is not currently a precondition for exercising restraint — a reviewer without the grant may still say "not yet" — and the set that decides which keys are preconditions is DEFINITION_RATIFICATION_GRANT_KINDS, in one place, where the test that enumerates the whole decision vocabulary against it will see any widening.

Capabilities are a separate scope class. A capability contract is shared by every product that consumes it, so a product grant must not reach it: a Vagary Voice grant may not authorize a change to the shared speech-to-text contract that Vagaris also depends on. That authority is capability:ratify_contract, carrying a {"capability": [...]} scope — one key with a scope, never one key per capability, because a permission vocabulary that is a function of tenant data is not a vocabulary. One capability per grant, on the same rule as products.

The Product Authority must not be an organization owner. That role auto-writes users:manage_permissions, and an account that can confer permissions can widen itself — which is the half of the authority model that says the ratifier holds no permission-management authority at all.

product:ratify_feature — like every governance:* key — is a decision-class permission: it decides for the organization rather than acting within it, so access grant refuses to confer one without a stated --reason, --retires-when and --review-by. The server refuses a product:ratify_feature scope missing retires_when or review_by at its own write door too, so the requirement holds for every writer of such a grant — the invite-join path included, not only this command. A grant already written without them keeps authorizing exactly what it authorized: the requirement binds new grants, and no read path was given a new refusal. An unexplained decision-class grant is the implicit-admin-fallback shape the platform's temporary-authority rule exists to keep out of the record. An operational permission (tasks:assign, users:invite, and the rest) needs none of this — the requirement is specific to the keys that confer standing to decide, not to permissions generally.

A grant that names no product authorizes nothing; org-wide breadth is spelled explicitly as --products '*' and still needs its own reason and retirement condition. access grant is read-modify-write — it preserves every OTHER grant's permission key and scope exactly. It does not leave those rows themselves untouched: the underlying route replaces the member's whole grant set in one transaction (setMemberPermissions, server/src/services/access.ts), deleting every existing row for the principal and re-inserting the read-modify-write's full array — so every other grant's row id, createdAt and grantedByUserId are reset, the last of those to whoever ran this grant. Practically: after granting product:ratify_feature, "who conferred a member's other permissions, and when" is no longer answerable from those grants' own rows — each now names the operator who ran this command and the moment they ran it, not whoever actually conferred that permission.

When the FY review ends, revoke the delegation the same way it was granted — access revoke is the same read-modify-write, in reverse:

vagaris access revoke <memberId> --company-id acme-co --permission product:ratify_feature

Two things product:ratify_feature can never be, by the same rule that lets it be delegated at all:

  • Conferred by a service principal. A binding may use delegated authority; it may never create one. Only a human board principal holding users:manage_permissions can run access grant.
  • Self-granted. access grant's target is a named member, and the server refuses a decision-class grant where the grantor and the grantee are the same principal — a member cannot promote their own standing to decide for the organization.

Promote and commission​

vagaris feature promote agent-versioning --decision <decision-id> --target TENANT_ZERO_PROVEN
vagaris feature commission agent-versioning --target TENANT_ZERO_PROVEN --gaps <assessment-ids>

Promotion names the decision that authorised it. There is no way to promote a feature no human ratified.

Commission refuses a gap whose target nobody ratified:

409 target_undefined — dimensions 11, 19 have no ratified or inherited target.
Define them first: vagaris feature definition write <feature>

That refusal is the point. Work created against a target nobody chose is work nobody asked for, and the system would rather tell you the target is missing than invent one.

The one exception is an explicit RATIFY_AS_EXPERIMENT decision, which authorises a bounded discovery mission and records on the resulting opportunity that it was experiment-authorised.

After a commission decision selects execution, a new mission with an assigned agent and a bounded workspace moves to todo and requests execution through the normal assignment controls. Missing placement leaves the mission in backlog with its reason. Agent pause, budget and execution policies still apply; check the linked mission's run to establish whether work actually started.

In the workspace, open Opportunities to review the commission's department assessments. Choose an explicit decision, strategy or reason, and rationale, then Record decision. A resulting mission appears as a link; a no-work decision records its rationale without creating a mission.

Defining a product or capability baseline​

A feature inherits targets from the product that owns it and the capabilities it consumes. Those baselines are definitions too, written and ratified the same way, on the subject rather than the feature:

vagaris definition get capability identity
vagaris definition write capability identity --content-file ./identity-baseline.json
vagaris definition decide capability identity --kind RATIFY \
--target TENANT_ZERO_PROVEN --ratified-dimensions 19,21 \
--rationale "Every consumer of identity inherits the tenancy and security floor"

A ratified baseline is what makes an inherited target actionable on every feature beneath it. An unratified baseline stays proposed, and Commission refuses gaps that lean on it.

Before deciding a baseline you can ask the door what it will say to you, exactly as for a feature — a capability needs an org-wide grant, since a product-scoped one does not span it:

vagaris definition authority capability identity

Rendering a definition package​

vagaris definition render feature agent-versioning --out ./agent-versioning.md
vagaris definition render capability identity --out ./identity.md

A capability or product package rolls up its own baseline targets plus every feature that consumes it. The document is an export: it carries the subject id, the definition version, a content hash and the build revision in its header, so a stale copy can never be mistaken for the record. The structured record stays authoritative.

Where the same answers live​

Every command here is a thin projection over one server contract. The Console's Definition Mode, the vagaris CLI and the Vagaris MCP tools read the same derivation, and a test drives one operation through all three and asserts the answers are byte-identical. If they ever disagree, that is a defect in the projection and not a difference of opinion.