# rosetta-research plugin

Seven-stage Product Family Research Pipeline. Takes a Salesforce Data
Foundations product family from corpus consultation through to canonical
graph content, which Stage 7 submits to the `rosetta-app-server`
ingestion API as a content:ingest bundle (since 0.19.0, the DT-RX-036
switch; ADR-PL-024: the production graph is the system of record for
content).

- **Latest shipped bundle:** the current `rosetta-research-<version>.zip`
  in this directory, regenerated by `make plugin-bundle-rosetta-research`
- **Slash commands:** `/rosetta-research:intake`, `/rosetta-research:extract`,
  `/rosetta-research:author-nodes`, `/rosetta-research:author-patterns`,
  `/rosetta-research:provenance`, `/rosetta-research:author-adrs` (v0.13.0),
  `/rosetta-research:validate`, plus
  `/rosetta-research:run-all` (v0.7.0 orchestrator — runs all seven stages
  in one chat). All eight commands accept a v0.8.0 opt-in
  `--bootstrap` flag and a v0.10.0 opt-in `--target-patterns=N`
  flag (`PRODUCT_FAMILY [--bootstrap] [--target-patterns=N]`).
  Since v0.16.0 every run also names its evidence inventory with the
  **required** `--dataset=<id>@<n|latest>` (a pinned `rosetta-ugc`
  release, e.g. `--dataset=d360-core@2`); see § "Evidence inventory:
  dataset releases (v0.16.0)". Since v0.26.0 a run may instead name a
  knowledge base with `--kb=<domainKey>` and read its release list from
  the app-server; see § "Knowledge base release lists (v0.26.0)".
  `--bootstrap` is for foundational runs that initialize the
  graph from a clean slate. `--target-patterns=N` (bootstrap-only,
  cap N=30) asks Stage 4 to author N patterns; if corpus capacity
  is short, Stage 4 pauses and surfaces three options. Two profiles
  replace that range: `guidance/waf` takes 1..40 and
  `reference/d360-mcp-kb` (v0.27.0) takes 1..60, each in either mode.
  `product-docs/data360` keeps the default.
- **Profiles:** `--profile=guidance/waf` (v0.15.0),
  `--profile=product-docs/data360` (v0.20.0) and
  `--profile=reference/d360-mcp-kb` (v0.27.0). Each is one file under
  `skills/product-family-research/references/profiles/`.
- **Skill:** `product-family-research`
- **License:** Proprietary — © 2026 Keenan Vision LLC (see the repo LICENSE)

For the full method spec, read `skills/product-family-research/SKILL.md`.
The extracted form under `util/plugins/rosetta-research/` is the source
of truth per Approach C; the shipped zip above is regenerated from it
via `make plugin-bundle-rosetta-research`.

## The run's review page on Curation > Runs (v0.28.1)

Once the run's lifecycle row exists, Stage 7 renders the run's HTML review
page and uploads it with `make report-family-publish BUNDLE=<run folder>
URL=<base>` (`commands/validate.md`, `references/run-lifecycle.md`). The
console's Curation > Runs opens it in a new tab; the server side is
platform MySQL 055 and app-server API 1.45.0. The upload is not a lifecycle
transition, and a failed one never stops delivery.

## The replacement runs' contract fixes, in every profile (v0.28.0)

Platform E3-S4-WP69. The d360-mcp-kb@1 run and E3-S4-WP68 each closed the
same gaps with an operator ruling or a run-local script. 0.28.0 writes those
fixes into every profile, the no-profile default included:

- **Names.** Stage 7's paraphrase check (E8-I6) never receives documented
  names. A PlatformObject's `name`, `label` and `alternateNames`, any node's
  `alternateNames`, and a SourceArtifact's `title` are exempt. A
  PlatformObject `label` must still start with a letter or digit and contain
  no `"`, `{` or `}`.
- **Unique properties.** Stage 3 checks `VocabularyTerm.term` and Stage 4
  `ContextPattern.patternId` against a restored content snapshot. Before the
  commit, Stage 7 re-checks those two and `ADR.adrId`,
  `SourceArtifact.artifactId` and `SourceArtifact.ugcDocumentId` against the
  newest graph read. Under
  Q-1-1 (Vernon Keenan, 2026-10-31), an exact held term is reused by
  `qualifiedKey`. A case or plural variant is reported and blocked. MANIFEST
  records `vocabulary_reused` and `unique_property_blocked`.
- **Bundle limits.** The builder refuses more than 20,000 CITED_BY evidence
  references, and an anchored CITED_BY whose SourceArtifact is outside the
  bundle. `make content-ingest-build SPLIT=yes` splits a run into
  dependency-ordered parts, with a proof that they equal the single bundle.
- **SemanticEntity `entityType`** is `entity`, `event`, `relationship` or
  `attribute`, and nothing else.
- **Thin patterns** are folded into a host or blocked, never padded
  (MANIFEST `consolidation`).
- **Review page.** `make report-family BUNDLE=<stage 7 run>` reads
  `product_family: data360`.

## Knowledge base release lists (v0.26.0)

`rosetta-meta` DT-RX-063 (RATIFIED 2026-10-29), platform E3-S4-WP66. A
knowledge base (a canonical domain such as `salesforce:rosetta-core`) owns
an ordered list of up to 8 frozen `rosetta-ugc` releases, edited on its
Knowledge room page with a revision history. After a switch, rebuild with:

```text
/rosetta-research:run-all <family> --kb=<domainKey>
```

- **Stage 1 reads the list once** (`GET /api/v1/curation/knowledge-bases/{domainKey}/datasets`,
  app-server API 1.43.0, with your `ROSETTA_API_KEY`), fetches each release's
  manifest from `rosetta-ugc` as `--dataset` does, and requires the served,
  recomputed and listed hashes to agree, with each release frozen. The list
  replaces the pin registry as the third hash, so no pin PR is needed.
- **The bundle freezes the list** (`corpus/kb-list@<revision>.json`) beside
  the manifests. MANIFEST.md records `kb_domain_key` and `kb_list_revision`
  with the usual `dataset…` fields; later stages copy them and never re-read
  the list. Lifecycle reports pass `MANIFEST=`, so revision 1 carries
  `kbListRevision` (`references/run-lifecycle.md`).
- **One way in.** `--kb` and `--dataset` are mutually exclusive. `--dataset`
  and `references/datasets.md` work unchanged (DT-RX-063 Q-2-1). The Data 360
  text-only profile keeps its `profilePins` there and runs with `--dataset`
  (Q-3-1).
- **Fail closed.** An unreachable app-server, an unwritten (revision 0) or
  empty list, a release that is not frozen, or any hash mismatch stops the
  run. Nothing falls back to `datasets.md`.
- **Removing a release from a list retracts nothing** (Q-6-1): it changes
  what the next run reads. DT-RX-061's activation switch takes a release's
  content out of live retrieval.
- `make kb-datasets KB=<domainKey> OUT=<dir>` in a `rosetta-platform`
  checkout runs the same list read and checks, for an operator who wants to
  look before a run.

## Every ContextPattern ADDRESSES a BusinessIntent (v0.25.0)

`util/plugins/decision-traces/dt-pa-010-product-docs-business-intent-coverage.md`
(RATIFIED 2026-10-29) closes the gap that left most WP41 Data 360 patterns
unreachable by retrieval's graph path. Under every profile, Stage 7
hard-fails a ContextPattern that ADDRESSES no BusinessIntent unless MANIFEST
`bi_coverage_exceptions` names an operator ruling for it, and MANIFEST gains
`bi_coverage`. Under `--profile=product-docs/data360`, reconciliation
synthesizes BusinessIntents from the run's own patterns, grounded in a
subset of their citations and never invented; an existing BusinessIntent is
reused only at a full fit; each pattern has 1 or 2 ADDRESSES at relevance
0.8 or 0.6; and `util/python/d360_authoring` fails before bundle build with
a `D360-BI-*` code (platform E3-S4-WP64).

## Tech Notes outrank conflicting SF material and must be cited (v0.24.0)

`rosetta-meta` DT-RX-059 (RATIFIED 2026-10-26) makes an admissible Tech Note
the highest-weight evidence in a run. Where a note and an `SF` item make
conflicting claims a node relies on, the node follows the note and does not
cite the `SF` item; each conflict is a row in `extraction.md` § "Tech Note
precedence". Two conflicting notes, or an `SF` item revised after the note's
approval, stop the run. Every admissible note document in the run's
inventory must be cited by at least one authored ContextPattern or ADR, or
Stage 7 fails; there is no flag to skip one. Under the Data 360 profile
every `tech-note` member must be `cited` (platform E3-S4-WP58).

## Tech Notes as dataset members (v0.23.0)

`rosetta-meta` DT-RX-055 (RATIFIED 2026-10-25) retires the `rtn-core`
dataset. A Rosetta Tech Note's SME-approved version is a member of whichever
datasets include it, under each dataset's own access rules, so a run reads
notes through the releases it already pins (`d360-core`, `waf-core`, and so
on). An `RTN` member of a registered release that is not `engagement`-scoped
is primary evidence; the app's content:ingest dry run proves its approval,
and a note that is not provably approved is refused, never downgraded to
`KV`. One note at one version in two releases is one item; one note at two
versions stops the run. Under `--profile=guidance/waf` an `RTN` member skips
the pair-file filter and is read from its release blob. The `rtn-core@1` pin
and the `make rtn-*` importer are gone (platform E3-S4-WP56).

## Authoritative run lifecycle (v0.22.0)

Every connected execution has one stable research ID from Stage 1 and an
exactly increasing revision stream across all seven stage-specific artifact
IDs. Each command reports its `running`, `blocked`, `failed` or `cancelled`
transition through `make research-run-report`; Stage 7 alone may report
`completed`, and only with the event ID returned by committed content
ingestion. The full retry, scope, unavailable and MANIFEST contract is in
`skills/product-family-research/references/run-lifecycle.md`.

The app-server ledger, not `IngestionEvent`, is authoritative for research
lifecycle. Reporting is producer-only and confers no review-verdict rights.

## Governed RTN external citations (v0.21.0)

An admissible `RTN-NNN` note is no longer a SourceArtifact this plugin
authors. Stage 5 and Stage 6 reference the existing governed note-document
node at `salesforce:rosetta-core::sourceartifact:ugc-<documentId>` and carry
the frozen dataset item's exact `ugc:version/<versionId>` and
`sha256:<64 lowercase hex>` values on every `CITED_BY`. The bundle builder
refuses a UGC node write, alternate/run-domain key, incomplete or malformed
tuple, tuple on an ordinary source, and direct attachment shortcut. This is a
source candidate under DT-RX-048; publication and runtime compatibility await
the separately reviewed app verifier and release sequence.

## Statuses and enums (v0.18.0)

Every new ContextPattern is `draft` and every new ADR and BusinessIntent
`proposed`, the SME review lane's pending states and the only statuses the
ingestion API takes: a run never writes a ContextPattern `validated`, an ADR
`accepted`, a BusinessIntent `active` or a status-less one, and a node
already in the graph is MATCHed by `qualifiedKey`, never re-emitted with its
graph status (platform E9-S1-WP14; `commands/run-all.md` § "Statuses").
`--hard-promote` is refused. A corpus `Deploy` phase maps to `Validate`, and
`VocabularyTerm.category` may be `concept` (metamodel v1.16.0), which the
app-server's content:ingest catalog has taken since v0.10.0
(`docs/decision-traces/e9-s1-wp2-plugin-ingestion.md` Q-B-1, Q-C-3).

## Ingestion: the only path (v0.19.0)

Since the DT-RX-036 switch (platform E9-S2-WP1; ADR-PL-024 in effect) every
run delivers a `rosetta-app-server` content:ingest bundle, and nothing else:

- **No seeds target.** No stage writes into a repo's `db/seeds/`, which left
  `main` at the switch. The Stage 3–6 Cypher stays inside the run's bundle
  (bundle-internal `db/seeds/` paths) as the review artifact and the source
  Stage 7 derives the bundle from (`make content-ingest-build` in the repo
  snapshot). `--target=ingestion` is a no-op alias for this release;
  `--target=seeds` is a hard fail that points at the frozen baseline tag
  v0.27.0, which holds the historical seed corpus and the last tagged
  seed-era plugin (0.18.0).
- **Delivery.** Stage 7 submits the bundle through MCP
  `rosetta_ingest_content` (preferred) or a REST POST to `--ingest-url`
  (default `https://api.rosetta-design.com`), always a dry run first, and
  commits only after the operator confirms the graph target in the session.
  The production API is a legitimate target; ingestion needs a platform
  admin's key. Content lands at `draft` / `proposed`, and only SME review
  moves it.
- **Existing content comes from the graph:** the rosetta MCP read tools
  live, or offline a local graph restored from a content snapshot
  (`make content-snapshot-restore`). Never an old repo zip's `db/seeds/`.

Details: `commands/run-all.md` § "Ingestion" and § "Existing content";
`commands/validate.md` Steps 4b and 4c.

## ADR authoring — new Stage 6 (v0.13.0)

v0.13.0 grows the pipeline from six stages to **seven** by inserting a
new authoring stage, `author-adrs`, between Provenance (now Stage 5)
and Validate (now Stage 7). The new Stage 6 emits corpus-baseline
`ADR` Cypher as a first-class canonical node type — joining the six
node types Stages 3-4 already author — with `(adr)-[:GOVERNS]->(cp)`
edges to the patterns each decision constrains,
`(sa)-[:CITED_BY]->(adr)` edges reusing the governed patterns'
SF-tethered sources, and a `ProvenanceEvent` per ADR
(`performedBy="rosetta-research:author-adrs"` — honest plugin-run
attribution). It runs after Provenance so the GOVERNS targets
(`ContextPattern`, 08L) and ordinary or governed-external CITED_BY source
identities (09L) are established; the ADR file (`<NN>L-<product>-adrs.cypher`, generally
source-artifacts-prefix + 1, e.g. `10g`) loads after them. Each family
carries 3-7 corpus-baseline ADRs, one per major irreversible
architectural decision shared across patterns (not one-per-pattern).
This is the sustainable, plugin-authored path the transitional
hand-authored `db/seeds/10g-data-360-adrs.cypher` converges toward;
v0.13.0 builds the path but does not retire `10g`. Ratifying DT:
`docs/decision-traces/d360-adr-authoring-plugin-path.md` (Option 1,
conditional on the structural-constraint precondition); operationalized
by E3-S2-WP11.

The ADR emit is born behind the **three structural-constraint layers**
that closed the v0.12.0 enum drift, not free-form runtime authoring:
(1) **templated emit** — `commands/author-adrs.md` ships concrete
`MERGE`-on-`qualifiedKey` templates with "STOP and surface; do NOT
substitute a close-enough value" language on every enum-valued field
(`status`, GOVERNS `scope`); (2) **static self-gate** — Stage 7
(`validate`) hard-fails on ADR shape, GOVERNS contract, and
provenance-coverage violations and refuses to ship a non-conformant
bundle; (3) **live-graph gate** — `scripts/neo4j/d360-invariants.sh`
gains INV-D16 (ADR shape), INV-D17 (GOVERNS contract), and INV-D18
(ADR provenance coverage), enforced at load time alongside
INV-D1…D15. No `db/schema/` change — the existing `ADR` label and
`GOVERNS` contract are used as-is.

## Guidance-corpus profile (v0.15.0)

`--profile=guidance/waf` runs the pipeline over the Salesforce
Well-Architected Framework corpus instead of a product family. The
profile is one file, `skills/product-family-research/references/profiles/waf.md`;
the rules that use it sit in delimited `<!-- BEGIN if-profile guidance -->`
blocks in each command and reference. Without the flag, nothing changes.

- **One run per pillar pair.** The PRODUCT_FAMILY argument names the pair
  (`waf-trust`, `waf-reliability`, `waf-operational-excellence`,
  `waf-resource-and-cost-optimization`, `waf-fairness`). Trust runs first
  in bootstrap mode; the rest run in extension mode.
- **Local corpus (0.15.2).** The run is a Claude Code session in the
  checkout. It reads the corpus in place from
  `outputs/tools/waf-capture/<run-id>/normalized/` (Markdown plus
  `manifest.json`), with no upload and no Drive
  (`docs/decision-traces/waf-local-corpus-run.md`, RATIFIED 2026-10-01).
  Stage output goes to `outputs/plugins/rosetta-research/<run-id>/bundle/`.
- **Domain split.** Everything a WAF run mints is `salesforce:waf-core`,
  platform facts included (0.15.4). A platform object, engine function or
  semantic entity already in the graph for the same fact is reused, never
  re-minted.
- **Gates.** Up to 40 patterns in either mode; ≥2 ADDRESSES / ≥0
  REFERENCES / ≥2 RECOMMENDS per pattern; ≥3 `evidenceAnchors` per pattern;
  3–12 ADRs; every file in the pair consumed; no web sources.
- **Draft only.** `--hard-promote` is refused.

```text
/rosetta-research:run-all waf-trust --dataset=waf-core@1 --profile=guidance/waf --bootstrap --target-patterns=30
```

Ratified by `docs/decision-traces/rosetta-research-v0.15.0-guidance-corpus-profile.md`
and `docs/decision-traces/waf-core-domain-key.md` (platform Epic E8,
E8-S1-WP2).

## Data 360 text-only profile (v0.20.0)

`--profile=product-docs/data360` is the revised Data 360 authoring path. It
accepts exactly one immutable, registered text-only derivative release. Stage 1
verifies its manifest, exact qualified scope-inventory hash, d360-core@2
lineage, complete retained/excluded reconciliation, and every derivative blob,
then seals those bytes. Stages 2–7 read only that sealed snapshot.

The profile refuses `d360-core@2` as authoring input, supplemental links,
tables/table rows, graphics/OCR, visual-dependent passages, web/search/Drive
sources, additional datasets, wrong versions, excluded anchors, and stale or
unscoped caches. It requires no companion, trusted-reviewer list, or visual
receipt. Exclusion is not acceptance, and its complete retained-scope coverage
is not full-corpus coverage.

The authorized publication is registered as `d360-text-only@1`, manifest
`sha256:14111f89d51531a042f963e94f7a2dfc3a07f7673d8d7ae49fd49fd1a7907a4e`,
with its exact inventory version and hash. The profile still fails before
content reads on any mismatch. See
`skills/product-family-research/references/profiles/data360.md`.

Since v0.23.0 the profile's pins are a list: `d360-text-only@1` stays WP41's
pin, unchanged, and `d360-text-only@2` is registered as **pending** (not yet
published; a run on it refuses). `@2` keeps the same derivative and scope
inventory and adds `tech-note` members: SME-approved Markdown Rosetta Tech
Notes, placed after the articles and before the inventory, outside block
reconciliation, cited only through the DT-RX-048 external endpoint
(rosetta-meta DT-RX-055 T4, T7, T8; platform E3-S4-WP57). Publishing `@2`
and installing its runtime pin are operator acts on Vernon's separate go.

## Reference profile (v0.27.0)

`--profile=reference/d360-mcp-kb` is the first profile of kind `reference`.
It runs over `d360-mcp-kb@1`, the Data 360 MCP knowledge base: 3,421 pages,
almost all per-object reference pages. The profile is one file,
`skills/product-family-research/references/profiles/d360-mcp-kb.md`, and
the rules that use it sit in `<!-- BEGIN if-profile reference -->` blocks in
the commands and references. Without the flag, nothing changes.

```text
/rosetta-research:run-all data-360 --dataset=d360-mcp-kb@1 --profile=reference/d360-mcp-kb [--bootstrap] --target-patterns=<N up to 60>
```

The mode is the operator's choice: pass `--bootstrap` or leave it out for
extension mode. The run's N and mode wait on Vernon's go (dt-pa-011
§ "After the merge").

- **Entry.** PRODUCT_FAMILY is `data-360`. The run names exactly one
  `--dataset=d360-mcp-kb@1`. `--kb` and `--hard-promote` are refused.
  `--target-patterns=N` accepts 1..60 in bootstrap or extension mode.
- **Local run.** The profile runs as a local Claude Code session in the
  operator's checkout, normally `run-all` in one session, with output under
  `outputs/plugins/rosetta-research/<run-id>/`. Stage 1 seals the verified
  page bytes in its run folder's `working/corpus-cache/<sha256-hex>`
  (MANIFEST `corpus_cache`). Stages 2–7, the generator and Stage 7's
  re-derivation read only that cache, re-verify each hash and never
  refetch. A stage that cannot read the cache, such as a fresh Claude.ai
  chat, stops.
- **Practice lane.** The default pipeline, up to 60 patterns. The default
  gates hold: ≥3 ADDRESSES, ≥3 REFERENCES, ≥3 RECOMMENDS, ≥2 cross-pattern
  edges and an SF tether per pattern, 3–7 ADRs, and the BusinessIntent
  coverage gate (v0.25.0). The product-docs BusinessIntent synthesis and its
  ≤2 rubric do not apply.
- **Catalog lane.** Stage 3 runs a generator in the run's `working/tools/`
  that executes the profile's spec. It mints one PlatformObject per
  documented object: 972 standard DMOs, 165 connector types and 801
  standard mapping templates, 1,938 in all. The generator emits no edge.
  Stage 5 adds one SourceArtifact per cataloged or reused page, with one
  CITED_BY, the catalog lane's only edge. No model authors a catalog fact.
  Field lists, mapping field pairs and connector parameters stay in the
  release.
- **Key space.** `salesforce:rosetta-core`, token `data-360-mcp-kb`, letter
  `h`; `references/uid-allocation.md` records `h` for this profile, and the
  next product family takes `i`. Each template-matched page holds one
  number N from 1001, in code point order of `Source path` across the three
  families, for `po-h-N` and `sa-h-N`. An existing object with the same
  `apiName` is reused, never re-minted, so the run needs a restored content
  snapshot; a reused page leaves `po-h-N` as a gap and keeps `sa-h-N`.
- **Stage 7.** It re-derives and gates every catalog node. Before the
  build it counts the run's nodes and edges against the content:ingest
  limits; over a limit, or the builder's "split the run" refusal, is a
  pause, never a Cypher fix. It draws a 25-node sample (12 DMO, 8
  standard-mapping, 5 connector) whose seed, population and draw come from
  the research run id by the profile's `gates.sample`; the commit waits for
  a named go on it (`catalog_sample_go`). MANIFEST records one disposition
  per page and the coverage figure.
- **Statuses.** Patterns land `draft`; ADRs and BusinessIntents land
  `proposed`.

**0.27.1.** The replacement run committed `d360-mcp-kb@1` on 2026-10-30
(eventId `data-360-data-360-bootstrap-stage1-20261030-160351`), so a further
run over `@1` is extension mode under its own ruling. The patch folds in
the four profile defects that run recorded, each as Vernon ruled it there:

- **Gate (f).** The paraphrase check runs on catalog descriptions. A
  catalog `name` and `label` are page-identity strings copied from the H1,
  exempt like SourceArtifact titles; a `label` must instead start with a
  letter or digit and contain no `"`, `{` or `}`.
- **Connector H1 debris.** The title rule drops the `Connector"}') ` or
  `"}') ` prefix seven `@1` connector pages open with. The connector
  family's `keyPins` keeps the four `connector-connector-…` slugs ingested
  on 2026-10-30, and a page whose key is already in the graph is reused by
  that key, so no key changes and no ingested string is rewritten.
- **Classes.** `classTable` adds Data Action Target → `endpoint`
  (DataCloud), Named Credential → `endpoint` (SalesforcePlatform) and
  External Client App → `interface` (SalesforcePlatform).
- **VocabularyTerm terms.** `vt_term` makes a term unique graph-wide, and
  the dry run cannot see a clash. Stage 3 checks every term against the
  graph, in bootstrap mode too, and reuses an existing one by
  `qualifiedKey` (MANIFEST `vocabulary_reused`); Stage 7 repeats the check
  before the commit.

Ratified by `util/plugins/decision-traces/dt-pa-011-research-reference-profile.md`
(RATIFIED 2026-10-30, Q-A-1 to Q-I-1), under `rosetta-meta` DT-RX-064
(platform E3-S4-WP67).

## Bootstrap mode (v0.8.0)

All eight slash commands accept an opt-in `--bootstrap` flag that
signals a foundational run: the graph is initialized from a clean
slate rather than extended on top of existing coverage. The flag
is parsed from `$ARGUMENTS` by literal-token match — pass it
exactly once at any stage (or once at the orchestrator) and the
mode propagates through the cumulative bundle's `MANIFEST.md`.

What changes in bootstrap mode:

- **Stage 1 (`intake`).** Step 5 (existing-coverage inventory) is
  skipped; `research-notes.md` records "Bootstrap mode — no prior
  graph content assumed." Corpus consultation, glossary capture,
  and alias-table normalization run normally.
- **Stage 3 (`author-nodes`) and Stage 5 (`provenance`).** UID
  ranges allocate from the canonical first slot per node label
  (`vt-<letter>-001`, `sa-<letter>-001`, etc.), contiguous, with
  no header comment claiming continuation of any prior range.
- **Stage 4 (`author-patterns`).** Cross-pattern wiring
  (`INTEGRATES_WITH` / `BUILDS_ON`) targets only patterns
  authored within the same bundle — no MATCH against an assumed
  pre-existing graph node. Bundles with fewer than 3 patterns
  cannot satisfy the ≥2 within-bundle wiring requirement; the
  plugin halts with a pause-and-ask.
- **Stage 7 (`validate`).** Three additional invariants fire:
  (a) every `MATCH` resolves to a node authored within the
  bundle (with the narrow canonical governed RTN citation exception,
  and from 0.28.0 each `vocabulary_reused` term); (b) UID ranges per node label are
  contiguous `001..N`; (c) header text and inline comments do
  not claim continuation of any prior range (soft warning).
- **Bundle.** Filename embeds `bootstrap` infix
  (`rosetta-research-<product>-bootstrap-stage7-<ts>.zip`);
  `MANIFEST.md` records `mode: bootstrap`.

What does **not** change: the v0.4.0+ corpus tethering, scope
discipline, glossary normalization, alias-table normalization,
five-field identity, ProvenanceEvent emission, and producer-only
contract all apply identically. Bootstrap mode affects how the
run interprets prior graph state; it does not relax any
constitutional or content-quality guard.

Omit the flag for normal extension runs (the v0.7.0 default).

## Evidence inventory: dataset releases (v0.16.0)

Since 0.16.0 the inventory is a frozen, numbered `rosetta-ugc` dataset
release (contract X-C6). The inventory CSV it replaced left `main` at
the E9 switch. Ratified by
`util/plugins/decision-traces/dt-pa-009-rosetta-research-ugc-dataset-manifests.md`.

- **`--dataset=<id>@<n|latest>` is required on every run** that does not
  name a knowledge base with `--kb` (v0.26.0; § "Knowledge base release
  lists (v0.26.0)"). There is no default. The release must be in the pin registry in
  `skills/product-family-research/references/datasets.md`
  (`d360-core@1`, `d360-core@2`, `d360-mcp-kb@1` (since 0.25.1),
  `d360-text-only@1` and `waf-core@1`; `rtn-core@1` was retired in 0.23.0);
  adding a pin is a reviewed
  plugin patch.
- **Stage 1 verifies and freezes it.** The served `manifestSha256`, the
  hash recomputed over `items[]` (RFC 8785) and the registered pin must
  agree. The verified manifest goes into the bundle as
  `corpus/<id>@<n>.manifest.json`; Stages 2–7 read only that copy.
- **Live or from a file.** A local session fetches it with
  `ROSETTA_API_KEY` (always with a `User-Agent`; Cloudflare refuses
  Python's default). A session that cannot reach the API passes
  `--dataset-file=<path>`, made with
  `make ugc-manifest DATASET=<id>@<n> OUT=<path>` in a checkout.
- **Nothing else changes.** Each item's `attributes` carries the old CSV
  columns verbatim, so the filters, routing, phase mapping and
  `Document name` identity below work as before. No CSV fallback exists.
- **Tech Notes (v0.17.0; dataset members since v0.23.0).** `--dataset` is
  repeatable (since v0.17.0). Since v0.23.0 there is no Tech Note dataset:
  an SME-approved note version is a member of the product releases that
  include it (`rosetta-meta` DT-RX-055). An item with `Scope = RTN` from a
  registered release that is not `engagement`-scoped is primary evidence,
  exactly as `SF` is, once the content:ingest dry run proves its approval; a
  note that is not provably approved is refused, never counted as `KV`.
  Curators include notes in datasets from the console; the `make rtn-*`
  importer was retired. Since v0.21.0 the note document is an external
  citation endpoint only; the plugin never clones or updates its governed
  SourceArtifact.

## Evidence-corpus discipline (v0.4.0+, refined v0.5.0, v0.6.0, v0.9.0)

The evidence-corpus contract described here was finalized at v0.9.0
and carries forward unchanged; since v0.16.0 it applies to the pinned
dataset release (above), whose item attributes carry the CSV columns:

- **Product Line filter (added v0.5.0).** Stage 1 filters the inventory
  to items whose `Product Line` matches the `$ARGUMENTS` product
  family before applying the scope and relevance filters.
- **Scope-aware admissibility.** The `Scope` attribute maps to
  the rosetta-platform domain hierarchy: `SF` rows ground canonical
  content, `KV` rows are admissible only as supporting citations
  alongside ≥1 SF source, `NTO` rows are out of scope for this
  plugin entirely (they belong to rosetta-archaeology).
- **Corpus-first source inventory.** Stage 1 consults the inventory
  before any web research; web sources are admitted only to fill
  documented Corpus Coverage Gaps.
- **`evidenceSources` on every canonical content node (v0.9.0).**
  v0.6.0 required `evidenceSources` on every authored ContextPattern;
  v0.9.0 extends the contract to every inheritable-label canonical
  content node — `VocabularyTerm`, `SemanticEntity`, `PlatformObject`,
  `EngineFunction`, `BusinessIntent`. Stage 4 (patterns) and Stage 3
  (other nodes) require ≥1 SF-scoped corpus reference, or an
  explicit `corpusGap=true` operator-acknowledged liability marker.
  Stage 5 emits `(SourceArtifact)-[:CITED_BY]->(node)` edges to every
  cited canonical node, not just patterns. Stage 7 (validate) mirrors
  the per-pattern gate onto all five sibling labels.
- **`corpusInventoryRef` / `corpusScope` / `corpusGap` on every
  SourceArtifact.** Stage 5 marks every source's origin and
  scope tier in the graph, with the same pair echoed in the
  ProvenanceEvent payload for C-6 audit.
- **Per-row INGESTED tracking retired in v0.5.0.** v0.4.0 emitted
  an `evidence-ingestion-delta.csv` for the operator to apply on
  PR merge; the source CSV no longer carries an `INGESTED` column,
  so the delta artifact is dropped. Sessions are now independent.
- **Glossary + product-name-alternatives normalization (added
  v0.6.0).** Two operator-curated reference tables at
  `docs/evidence/glossary.csv` (verbatim definitions for terms
  cited as `VocabularyTerm`, with a default `Type` filter that
  excludes general business jargon) and
  `docs/evidence/product-name-alternatives.csv` (canonical-product
  alias graph for Stage 2 product-reference normalization and
  Stage 3 `PlatformObject.alternateNames` authoring).
  `VocabularyTerm.aliases` and `PlatformObject.alternateNames` are
  new node properties added in v0.6.0.

Full protocol: `skills/product-family-research/references/evidence-corpus.md`.

Bundle regeneration after future edits is one Make target away:
`make plugin-bundle-rosetta-research`. See the
`util/plugins/README.md` "Bundles are derived" note.

## Installation

### Auto-loaded inside this repo (recommended for repo work)

When you run `claude` from a `rosetta-platform` checkout, this plugin
is auto-discovered via
`.claude/settings.json`. The
`/rosetta-research:*` slash commands become available automatically; first-run
users get a "trust this folder?" prompt, subsequent runs are silent.
No install step beyond the trust prompt.

This is the right path for plugin authoring and repo-internal use.
Cowork upload (next section) is the right path for production
research sessions where the user is iterating on uploads.

### Claude (web / Cowork)

1. Open a Cowork chat.
2. Customize → Plugins → **+** → upload the current
   `rosetta-research-<version>.zip` regenerated by
   `make plugin-bundle-rosetta-research`.
3. Confirm `/rosetta-research:intake` appears in the slash-command palette and that its argument-hint shows `PRODUCT_FAMILY --dataset=ID@N|ID@latest …` (v0.16.0 sanity check).

### Claude Code (CLI)

The bundle follows the standard plugin layout
(`.claude-plugin/plugin.json`, `commands/`, `skills/`), so it can also
be loaded as a local Claude Code plugin:

```bash
mkdir -p ~/.claude/plugins
unzip -o util/plugins/rosetta-research/rosetta-research-<version>.zip \
  -d ~/.claude/plugins/
```

Restart your Claude Code session and the `/rosetta-research:*` commands become
available. Refer to the Claude Code plugin documentation for
marketplace-style registration if you prefer a managed install path.

The plugin reads existing content and submits its bundle through the
rosetta connector (MCP) when the session has it, or over REST with a key
(`ROSETTA_API_KEY` to read; a platform admin's key, `ROSETTA_INGEST_KEY`,
to submit). It never reaches into your local checkout, your Neo4j, or your
MySQL.

## Inputs every session expects

Drop these in the chat's uploads area before invoking the slash
command:

| Stage | Required uploads |
| --- | --- |
| 1 — `/rosetta-research:intake` | `rosetta-platform-VERSION.zip`; plus the verified `<id>@<n>.manifest.json` when the session cannot reach `api.rosetta-design.com` (`--dataset-file`, v0.16.0) |
| 2 — `/rosetta-research:extract` | repo zip + `rosetta-research-<product>-stage1-*.zip` |
| 3 — `/rosetta-research:author-nodes` | repo zip + Stage 2 bundle |
| 4 — `/rosetta-research:author-patterns` | repo zip + Stage 3 bundle |
| 5 — `/rosetta-research:provenance` | repo zip + Stage 4 bundle |
| 6 — `/rosetta-research:author-adrs` | repo zip + Stage 5 bundle |
| 7 — `/rosetta-research:validate` | repo zip + Stage 6 bundle |
| All — `/rosetta-research:run-all` | repo zip (plus the manifest file as for Stage 1); `--dataset` (or `--kb`, v0.26.0) is passed once and travels in the bundle (v0.7.0 orchestrator runs Stages 1-7 in one chat; per-stage bundles produced as checkpoints; v0.8.0 `--bootstrap` flag propagates from the orchestrator invocation through every stage's bundle) |

Produce the repo zip from a clean checkout:

```bash
cd ~/path/to/rosetta-platform
git archive --format=zip HEAD > /tmp/rp.zip
```

Each stage emits ONE cumulative zip — Stage 7's bundle contains
everything Stages 1–6 produced plus the validation outputs. Re-upload
that bundle as the prior-stage input to the next stage.

## Delivering the output (v0.19.0)

The bundle is not applied to a checkout: since the switch a checkout has
no `db/seeds/`, and content reaches the graph only through the ingestion
API. Stage 7 (validate) builds the content:ingest bundle under
`outputs/plugins/rosetta-research/<run-id>/ingest/` and runs the delivery
step itself:

1. **Dry run.** MCP `rosetta_ingest_content` with `dryRun: true`
   (preferred), or the REST fallback to `--ingest-url` (default
   `https://api.rosetta-design.com`). A 422 lists every failure; the fix
   goes into the Cypher at the stage that wrote it, never into the bundle.
2. **Confirm.** The plugin shows the plan and names the graph target, and
   waits for the operator's confirmation.
3. **Commit.** The same bundle, committed. It lands at `draft` /
   `proposed`; an SME verdict in the review surface is the only way on.

When the session could not submit, or the operator stopped at the dry
run, `VALIDATION-CHECKLIST.md` (inside the bundle at
`outputs/plugins/rosetta-research/<run-id>/VALIDATION-CHECKLIST.md`) gives
the steps. From a rosetta-platform checkout, with a platform admin's key in
`ROSETTA_INGEST_KEY`:

```bash
make content-ingest-submit PAYLOAD=<bundle> URL=https://api.rosetta-design.com              # dry run
make content-ingest-submit PAYLOAD=<bundle> URL=https://api.rosetta-design.com COMMIT=yes   # commit, once the target is confirmed
```

There is no content PR, `make seed-diff`, `make intake-validate` or
`make prod-migrate` step: those read or load seed files, which left
`main` at the switch.

## ADR-PA-008 compliance (v0.9.0)

ADR-PA-008 establishes that plugin runtime
outputs (manifests, validation checklists, eval invocation scripts,
run logs) land at `outputs/plugins/rosetta-research/<run-id>/` in the
repo root, gitignored, never auto-promoted.

**v0.9.0 ships compliance.** The bundle's internal layout now
places every non-canonical artifact (`MANIFEST.md`,
`STATIC-ANALYSIS-REPORT.md`, `VALIDATION-CHECKLIST.md`,
`research-notes.md`, `extraction.md`, `sources.md`) under
`outputs/plugins/rosetta-research/<run-id>/` paths inside the
bundle, where `<run-id>` is `<product>-stage<N>-<timestamp>`
(extension) or `<product>-bootstrap-stage<N>-<timestamp>` (bootstrap).
The run's Cypher sits at bundle-internal `db/seeds/` paths per
ADR-PA-002; since 0.19.0 it never lands in a checkout.

Tracker entry in `util/plugins/README.md` §
"Migration to ADR-PA-008" drops `rosetta-research` from **TODO** to
**Complete** at v0.9.0.

## Constitutional alignment

The plugin operates within the rosetta-platform invariants
(`CLAUDE.md`): C-2 (Cypher canonical for schema; since the switch the
graph is the system of record for content, ADR-PL-024), C-4 (Make is the
user surface), C-5 + metamodel v1.19.0 §5 + §6 (five-field identity plus
`projectId`), C-6 (ProvenanceEvent on every mutation), C-7 (producer-only
— it never imports from `rosetta-app-server`; submitting a bundle to its
API is a runtime call, not an import).

The plugin never invokes `make`, `cypher-shell`, `mysql`, `docker`, or
any command on your machine. In its own workspace it runs the repo
snapshot's `make content-ingest-build`, and it submits only after you
confirm the graph target.

## Reporting issues

File against this repo with tag `[plugin]`. Include:

- the bundle filename (carries the version),
- which stage you were running,
- the upload manifest the plugin echoed in its first response,
- any failure the dry run listed (`ingest/dry-run.json`) or the
  builder's refusal text, and `VALIDATION-CHECKLIST.md`.
