diff --git a/docs/vrs/04-resource-reference/requirements.md b/docs/vrs/04-resource-reference/requirements.md new file mode 100644 index 00000000..fa931be6 --- /dev/null +++ b/docs/vrs/04-resource-reference/requirements.md @@ -0,0 +1,64 @@ +# Resource reference requirements + +## Context + +st2 has two durable edge types that point to a Resource: + +- An Agent Spec binding gives a Resource an agent-local name. +- A link record gives a Resource a relation and optional metadata. + +Both edge types use the same typed Resource identity. They have separate +authority, storage, mutation, projection, and lifetime rules. + +This document refines root requirements +[`R20` and `R21`](../requirements.md). It resolves +[st2 issue 122](https://github.com/compoundingtech/st2/issues/122) and keeps the +link boundary in [st2 issue 62](https://github.com/compoundingtech/st2/issues/62). +The root requirements have authority if the documents disagree. + +## Requirements + +- **RESOURCE-R01 One portable reference value:** Each binding and link record + contains one `ResourceRef`. It has exactly two required fields: a non-empty, + open `_tag` and an exact RFC 3986 absolute `uri`. st2 preserves both strings. + It does not resolve the URI or add authority, access, readiness, or lifecycle + policy. +- **RESOURCE-R02 Distinct edge identities:** A binding identity is the agent + identity plus binding name. A link identity is the agent identity plus durable + record ID. A URI is not an edge identity. Many edges can use the same URI. +- **RESOURCE-R03 Separate authority and lifetime:** Agent Spec authors and + catalog publishers control bindings. The owning agent controls its link + records. A change to one edge type does not create, change, or remove the + other edge type. +- **RESOURCE-R04 Separate projections:** Agent Spec readers and + `st2 agents --json` show bindings. `st2 resource` shows only link records. No + command provides one combined mutable Resource inventory. +- **RESOURCE-R05 Open link relations:** A relation is absent or is a non-empty, + open string. st2 has no closed relation registry. A relation does not give + mutation authority. +- **RESOURCE-R06 Lossless exact-URI grouping:** A read-only consumer can group + edges only when the `uri` strings are byte-for-byte equal. A group keeps each + edge, `_tag`, and metadata item. Grouping does not normalize a URI, select one + `_tag`, combine records, or change storage. +- **RESOURCE-R07 Explicit legacy-link transition:** Readers accept the + canonical `_tag` and `uri` form and the former `url` form. For a legacy `url`, + the reader uses the same scheme-derived default `_tag` that a new untyped link + uses. New writes use only the canonical form. A read does not rewrite a file. +- **RESOURCE-R08 Nondisruptive edge changes:** A change to only bindings or link + records does not change the effective task launch definition. It does not + stop, replace, or relaunch healthy work. + +## Acceptable tradeoffs + +- **RESOURCE-T01 Less precise compatibility default:** A URI scheme gives a + deterministic fallback type for an untyped legacy link. An explicit `_tag` can give + a more precise type. Compatibility is better than invented type knowledge. +- **RESOURCE-T02 Duplicate views preserve intent:** A consumer can see many + edges for one exact URI. Separate edges preserve their authority and metadata. + +## Non-goals + +- Resolve a Resource, add a Resource registry, or change an external object. +- Treat a URI as authority. +- Move linked outputs into Agent Spec. +- Require an external store, publisher, or controller for plain-folder data. diff --git a/docs/vrs/04-resource-reference/spec.md b/docs/vrs/04-resource-reference/spec.md new file mode 100644 index 00000000..dfae29e5 --- /dev/null +++ b/docs/vrs/04-resource-reference/spec.md @@ -0,0 +1,208 @@ +# Resource reference specification + +This document defines the shared value and the two Resource edge types in +[requirements.md](./requirements.md). + +## Status + +Draft. This document defines an API and migration contract for later work. + +## Data model + +The shared value is: + +```text +ResourceRef { + _tag: NonEmptyString, + uri: AbsoluteUri +} +``` + +`_tag` is an open discriminator. st2 preserves it and does not require +registration. `uri` uses the Agent Spec absolute-URI validation. st2 preserves +the URI byte-for-byte. A valid value does not prove that the Resource exists, +is reachable, or gives access. + +Each edge contains the shared value: + +```text +Binding { + name: NonEmptyString, + resource: ResourceRef +} + +Link { + id: RecordId, + relation: NonEmptyString?, + resource: ResourceRef, + title: String?, + tags: [String], + body: String? +} +``` + +An existing wire can keep the fields flat. The Agent Spec form stays: + +```kdl +resource "work" _tag="github-issue" uri="github-issue://example/project/123" +``` + +Its JSON projection stays: + +```json +{"name":"work","_tag":"github-issue","uri":"github-issue://example/project/123"} +``` + +The API uses one shared value. It does not require a nested `resource` object on +an established wire. + +## Identity, authority, and mutation + +| Edge | Identity | Source and writer | Mutation | +| --- | --- | --- | --- | +| Binding | agent + `name` | Agent Spec; author or catalog publisher | replace declaration | +| Link | agent + `id` | `resources/links/.md`; owning agent | add or remove one record | + +The URI identifies the referenced Resource. It does not identify an edge. Many +bindings and links can use the same URI without a conflict. + +A declaration change does not write to `resources/links/`. A link change does +not write an Agent Spec. Neither operation changes the object named by the URI. + +## Projections + +Agent Spec readers and `st2 agents --json` show bindings. The commands +`st2 resource add|ls|read|remove` show only link records. + +`st2 resource ls` does not show bindings, inbox state, context, or other durable +data. A consumer can read both projections and join them in memory. The joined +view is read-only. + +The link read model shows `id`, `_tag`, `uri`, `relation`, `title`, `tags`, and +`body`. Human `ls` output shows the ID, `_tag`, URI, and optional title. Human +`read` output labels `_tag` as `type` and labels the URI as `uri`, not `url`. + +## Link relation + +`relation` is optional open text. A present value is not empty. Values such as +`output`, `reference`, and `blocked-by` are conventions. Unknown values survive +reads and writes. + +The link tag gives the edge kind. `relation` gives the reason for the link. It +does not replace `ResourceRef._tag` or give authority. + +## Exact-URI grouping + +The only generic grouping key is the exact `ResourceRef.uri` string. A binding +and two links with `https://example.test/work/1` can form one read-only group. +If case, escaping, a slash, a query, or a fragment differs, the URI forms a +different group. st2 does not normalize the value or use the network. + +A group keeps every edge in stable source order. It also keeps every `_tag`. +Different tags for one URI remain visible. A consumer does not select one tag +without an explicit policy. Grouping does not delete or rewrite an edge. + +## Canonical link wire + +New link files use `_tag` and `uri` in YAML frontmatter: + +```markdown +--- +_tag: github-pull-request +uri: https://github.com/example/project/pull/123 +title: Resource reference contract +tags: docs, vrs +relation: output +--- +Optional notes. +``` + +The filename is the durable link ID. `title`, `tags`, `relation`, and the body +are link metadata. They are not fields on `ResourceRef`. + +The authoring command is: + +```text +st2 resource add [--type <_tag>] [--title ] + [--tag ...] [--relation ] [--body-stdin] +``` + +`--type` sets the Resource discriminator. `--tag` continues to add link-search +metadata. If `--type` is absent, the writer converts the URI scheme to lowercase +and writes it as `_tag`. Use an explicit type when the scheme is not precise. + +New writes use only `_tag` and `uri`. They do not write `url`. + +## Legacy `url` transition + +The link reader accepts exactly two forms: + +- The canonical form has `_tag` and `uri`. The reader validates both fields and + constructs the exact `ResourceRef`. +- The legacy form has `url`. The reader validates an absolute URI, derives the + lowercase scheme tag, and preserves the URL bytes as `uri`. + +A file fails to decode if it mixes `url` with `_tag` or `uri`, or if it has only +one canonical field. An invalid URI or an empty explicit `_tag` also fails. A +list operation reports the record ID and the error. It does not omit the record. + +The dual reader has no expiry in this contract. A read does not rewrite a legacy +record or change its ID or metadata. A remove operation removes only that record. +Each later add uses the canonical form. New state can converge without a fleet +rewrite. + +The scheme tag is a compatibility value. It is not domain knowledge. For +example, this legacy value: + +```text +https://github.com/example/project/pull/123 +``` + +becomes: + +```json +{"_tag":"https","uri":"https://github.com/example/project/pull/123"} +``` + +An author can instead add a new link with `_tag` `github-pull-request`. + +## Plain-folder behavior and task liveness + +Both edge types work from their current plain files: Agent Spec KDL and +`resources/links/*.md`. Parse, project, add, and remove operations need no +registry, resolver, remote service, or alternate content store. + +Resource data is not part of the effective task launch identity. A reconciler +can adopt changed Resource metadata. A Resource-only change cannot request a +stop, replacement, or relaunch. + +## Implementation map + +The implementation must add public `agent_spec::spec::ResourceRef`. It must use +one constructor and validator for both edge types. The existing +`agent_spec::spec::Resource` remains the binding wrapper. It delegates `_tag` +and `uri` to the shared value and keeps its accessors and flat serialization. +The link model also contains the same public value. + +The current owners are: + +- [`agent-spec::spec::Resource`](../../../crates/agent-spec/src/spec.rs) owns + binding fields and absolute-URI validation. +- [`agent-spec` KDL lowering](../../../crates/agent-spec/src/kdl_format.rs) owns + the flat binding syntax. +- [`st2::resource`](../../../src/resource.rs) owns link storage, parsing, and + rendering. +- [`ResourceCmd`](../../../src/main.rs) owns the link CLI. +- [`resource_only_changes_do_not_replace_or_relaunch_a_live_task`](../../../tests/reconcile.rs) + proves current nondisruption. + +Implementation evidence must prove: + +1. One constructor validates both edge types. +2. Agent Spec KDL and JSON projections do not change. +3. A canonical link write and read succeed. +4. A legacy read derives the scheme tag and does not rewrite the record. +5. A mixed or incomplete wire fails and reports the record ID. +6. Binding and link projections stay separate. +7. Exact-URI grouping keeps every edge and tag. +8. A Resource-only edge change does not replace healthy work.