Manual 0.8.0

Connector contract

Who this is for: anyone preparing files for a file-backed source, and anyone writing a connector to a source that has no files at all. You need: nothing installed — this page describes the engine, not your instance. Not here: how to write the connector (writing-a-connector.md) or how to enable one (engine-config-surface.md).

What Revetly reads from every source this engine ships a connector for, and what each field produces once it is read. Every connector in the image is below, enabled here or not: an instance enables the ones it names in [connectors] enabled, and enabling one changes nothing about what it declares.

For a file-backed source every key is optional: omitting one never fails an ingest, and never errors — it costs you the structure that key carries. That trade is the reason this page exists.

Run revetly check against your own files before revetly ingest. It reports, per file, which of these keys were read, which were ignored, which references point at artifacts that do not exist, and what the import yields in aggregate — without a provider call. It also names a work item whose file carries its ## Acceptance criteria list twice, which the pm connector would read as one list of twice the criteria; revetly check --repair folds each such file to one list, keeping the last, and records the repair on its review_log.

What a read may do to your sources

Every read of a source declares what it is allowed to do to it, and the two halves are named separately: whether the repositories behind the roots may be fetched and fast-forwarded, and whether this instance may record the date it first read one.

A connector itself fetches nothing and writes nothing outside that policy. extract() answers what the source holds; the engine's own sync, run under the policy the caller declared, is the only code that moves a repository. The conformance kit drives extract() under the read-only policy with every repository-moving call refused, and fails a connector that moved or wrote anything under it.

This page is generated from the connectors themselves (revetly check --contract), so it cannot describe a field in one way and read it in another — and it covers a connector that arrived as an installed plug-in or as a module inside the instance exactly as it covers one the engine ships. Writing one of those is docs/writing-a-connector.md.

The artifact envelope

Every connector, whatever it reads, hands back artifacts in this shape. The table is generated from the dataclass, so a field cannot be documented here and absent from the code. Required means undefaulted: leaving it out is a TypeError at construction, which is deliberate for provenance — a default would have the engine assert authority on a source's behalf.

Identity is the triple (source_system, source_instance, native_id); id is derived from it and is never something a connector picks. An open kind says what the artifact is, in the source's own word; a closed content_format says how its bytes are encoded.

Three more axes say how a fact should be weighed rather than where it came from, and all three are the connector's to declare — the engine never infers one from body text.

A fourth axis is about the record rather than the source, and it is the one field here that differs artifact by artifact inside a single store.

actors carries who the artifact is about, as structured references rather than a formatted string: (source_system, source_instance, native_id) plus an optional display name, email and role. Where the same human appears under different keys in different sources, a reference may declare same_as — and that is the only way two references are ever joined. The engine does not merge on matching names or matching emails, because two people share a name and one person has three addresses, and an inferred merge is unmergeable afterwards.

ingest_run is filled in by the engine and never by a connector: it names the run that produced the artifact, which the ledger keys the connector's version, config fingerprint and completeness outcome to. A connector may state its own version, and one shipped as a distribution should.

A connector also declares whether the view it just returned was complete (ExtractResult.complete / reason). Absence only means deletion when the run held a complete view: a rate-limited or half-paged source returning fewer artifacts is not a source that deleted them.

field type required
kind str yes
title str yes
body str yes
source_path str yes
connector str yes
provenance str yes
id str no
source_system `str None`
source_instance `str None`
native_id `str None`
same_as list no
record_source `str None`
relations list no
content_format str no
source_uri `str None`
source_locator dict no
unit str no
type `str None`
metadata `ArtifactMetadata dict`
outside_backlog `OutsideBacklog None`
immutable bool no
records_a_moment bool no
authority str no
confidence `float None`
sensitivity str no
attribution str no
attribution_issuer str no
attribution_actor str no
actors list no
source_created_at `datetime.datetime None`
source_updated_at `datetime.datetime None`
source_change_dates tuple no
source_changes_total int no
source_change_notes tuple no
source_change_notes_dropped int no
observed_at datetime no
ingest_run str no

What history each source can serve

A connector declares this without calling its source and without spending anything, so an instance can find out what a backfill could offer before enabling one. A declaration nobody makes is read as none, never as unknown — a source with no revision API is the ordinary case, and revetly backfill refuses it by name rather than returning an empty result that reads as "there was no history".

Three answers, because sources differ in all three and a single "history: yes" would make each of them lie about the others:

revetly connectors list and revetly doctor report these per enabled connector.

connector depths bounded slice of source time horizon
azure-devops state-transitions, field-revisions, comments yes revisions and state transitions: every revision Azure DevOps holds, back to the creation of the oldest work item in scope — ADO keeps them for the life of the item and prunes none of them. Comments: the whole thread on every work item in scope, read on the ordinary pass as well as on a backfill (beyond it: a work item that was DESTROYED takes its revisions and its thread with it — one in the recycle bin is merely absent and is a restore away. What a comment used to say before this instance first read its thread is not served unless comment_versions is on: the current version is what a thread read answers, and the versions endpoint is a request per comment)
commits commits yes every commit in the declared repositories, back to the first (beyond it: nothing before a repository's first commit, nothing uncommitted, and nothing at all from a declared root that is not a git repository)
conversations field-revisions yes every committed version of every file under this source's roots (beyond it: nothing before the first commit that touched them, nothing uncommitted, and no history at all for a root outside a git repository)
deliveries field-revisions yes every committed version of every file under this source's roots (beyond it: nothing before the first commit that touched them, nothing uncommitted, and no history at all for a root outside a git repository)
docs field-revisions yes every committed version of every file under this source's roots (beyond it: nothing before the first commit that touched them, nothing uncommitted, and no history at all for a root outside a git repository)
github state-transitions, comments yes transitions: every timeline event GitHub holds, per issue, read in 90-day windows on updated_at. Comments: the whole thread on every issue in scope, read under a per-pass bound on the ordinary pass (beyond it: comment edit history is GraphQL-only and is not read: the current version is what a thread read answers and an edited comment is re-read through its body hash. A transferred issue is not followed into its new repository)
pm field-revisions yes every committed version of every file under this source's roots (beyond it: nothing before the first commit that touched them, nothing uncommitted, and no history at all for a root outside a git repository)
todoist state-transitions, comments yes completed work: back to the scoped project's own creation date. Comments: the whole thread on every task in scope. State transitions: whatever the account's plan retains of the activity log — 7 days on Free, unlimited on Pro or Business, read from /user during the pass rather than promised here (beyond it: no field revisions, on any plan: Todoist's task API exposes no per-field change log, so what a field held before its current value is not readable and this connector will not pretend the absence is an empty history. A transition older than the account's activity retention is gone from the log — the completion itself survives in the completion feed, which is why completions are read from there and not from the log)

What each source says its own kinds are

Two axes a connector declares per artifact and nobody infers: sensitivity, how freely the content may be handled, and authority, how strongly the source claims it. [egress] withhold_above gates on the first, and it is the one withholding declaration that reaches an artifact nobody has met — a ticket that arrived this morning is governed by what its connector says it is, rather than by an id somebody listed.

Declared per kind, not per connector, because that is where the judgement differs. A tracker's work items are what the tracker is for and what a team already treats as shared; its comment threads are where somebody pastes a stack trace with a token in it or names the customer who reported the bug. One tier for a whole source either over-withholds the backlog or under-withholds the threads, and both are wrong in a way that only shows up on somebody else's data.

A connector that declares nothing is absent from this table, and its artifacts carry the engine defaults — internal and asserted — which is the reading that is never wrong about a source that said nothing. Nothing here changes behaviour for an instance that declares no threshold.

connector kind sensitivity authority why
azure-devops comment confidential asserted free text on a thread — where credentials and customer names are actually pasted, and a remark nothing vouches for
azure-devops comment-version confidential asserted free text on a thread — where credentials and customer names are actually pasted, and a remark nothing vouches for
azure-devops delivery-note confidential asserted a delivery note this engine posted, carrying a marker that names the local record it renders
azure-devops pbi internal declared the work item's own fields, held by Azure DevOps as its record
azure-devops revision internal declared the work item's own fields at a past moment, so it stands as the work item does rather than as a thread does
azure-devops any other kind internal declared the work item's own fields, held by Azure DevOps as its record
github comment confidential asserted free text on a thread — where credentials and customer names are actually pasted, and a remark nothing vouches for
github delivery-note confidential asserted a delivery note this engine posted, carrying a marker that names the local record it renders
github issue internal declared the issue's own fields, held by GitHub as its record
github pbi internal declared the issue's own fields, held by GitHub as its record
github pull-request internal declared the pull request's own fields, held by GitHub as its record
github transition internal declared the issue's own fields at a past moment, so it stands as the issue does rather than as a thread does
github any other kind internal declared the issue's own fields, held by GitHub as its record
todoist comment confidential asserted free text on a thread — where credentials and customer names are actually pasted, and a remark nothing vouches for
todoist completion internal declared the completion feed's record that this task was finished, and when
todoist delivery-note confidential asserted a delivery note this engine posted, carrying a marker that names the local record it renders
todoist pbi internal declared a task's title and description, held by Todoist as its own record
todoist transition internal declared an entry in the account's own activity log, stating what changed
todoist any other kind internal asserted —

azure-devops connector

Not file-backed: work items from the Azure DevOps REST API (wiql + workitemsbatch, $expand=Relations) over the organization, project and scope [connectors.azure-devops] declares. revetly check reports this declaration rather than parsing frontmatter that does not exist.

Settings — [connectors.azure-devops]

Which Azure DevOps organization, project and work item types this instance reads, and how this board's vocabulary maps onto its own.

Read by connector 'azure-devops' and write adapter 'azure-devops' — one table, one parse, so the two halves of this backend cannot be pointed at different places.

key type default accepts what it is
token_env str AZURE_DEVOPS_PAT Names the environment variable holding the personal access token, never the token.
organization str required The organization as it appears in the URL.
project str required The team project inside that organization.
area_path str empty Narrows the read to one area path and everything under it.
query str empty A saved query id whose result is the scope, whole.
work_item_types list of str empty Which work item types are in scope; empty reads every type the map names.
types table empty This board's own type names onto the item types this project declares.
fields table empty Where each type keeps the body this engine reads and writes.
states table empty This project's delivery statuses onto the board's own states.
stamp_fields list of str empty Extra board fields kept as queryable node properties.
history_fields list of str System.State, System.AssignedTo, System.Description, Microsoft.VSTS.Common.AcceptanceCriteria Which field movements count as history worth an episode.
comments bool true Whether an ordinary read pass reads comment threads; off is a cost lever.
comment_threads_per_pass int (threads) 0 How many comment threads one read pass may fetch; zero means every thread.
comment_versions bool false Whether a backfill reads each comment's earlier versions.
base_url str https://dev.azure.com The host every Azure DevOps call goes to, for a server installation.
api_version str 7.1 Which Azure DevOps REST API version every call names.
timeout float (seconds) 30.0 Seconds one API call may take before the read pass gives up.

A key not listed above is kept and read by nothing — never dropped and never refused, because the engine rules on no key it was not told about.

Connection: token_env, organization, base_url, api_version, timeout. These establish communication with the tool; every other key above is scope. On an instance with more than one project, a project's own table that states none of them inherits exactly these from the instance's table, and one that states any of them states its own connection whole. Scope is never inherited.

key shape what it does effect
System.Id one value Azure DevOps' own key for the work item, which becomes this artifact's native_id — the graph id is azure-devops:<org>/<project>:<id> content
System.Title one value the work item's title, which becomes the artifact title and the heading of the episode body rides the episode body — changing it re-pays extraction
System.Description one value the description, converted from HTML to markdown deterministically. It rides the episode body, so extraction re-runs when the ask changes and not when the board does rides the episode body — changing it re-pays extraction
Microsoft.VSTS.Common.AcceptanceCriteria one value the acceptance criteria, converted the same way and landed under an '## Acceptance criteria' heading — the same shape a repo story has, so a delivery record can read them back verbatim rides the episode body — changing it re-pays extraction
System.WorkItemType one value the work item type, translated onto one of this project's PBI types by [connectors.azure-devops.types] — "User Story" = "story", Bug = "bug". Everything that map names ingests as kind = "pbi", whatever this board calls it, with the project's word in artifact_type so a backlog read out of two stores can be compared, and this word — the board's own — kept here in work_item_type. A type the map names nothing for ingests carrying this word as kind, with no PBI type and outside the hierarchy, and is never silently filed as a story. Where two source types wear one project type, { type = "story", create = true } says which of them a new work item becomes; nothing guesses it. Where a type keeps its body is [connectors.azure-devops.fields] with Bug = { body = "Microsoft.VSTS.TCM.ReproSteps" }, read by the write half and this one, so the ask and its citation trailer round-trip through the same field recorded as a queryable node property — changing it sends no episode
System.LinkTypes.Hierarchy-Reverse one value the work item this one rolls up under — a deterministic parent edge, never an LLM guess mints a deterministic parent edge; an unresolved target enters as an external reference
relations one value every other link ADO states — Related, Dependency-Forward/Reverse, Successor/Predecessor — carried as a source-declared edge under ADO's own relation word. An ArtifactLink to a pull request or a commit anchors as an external stub; a hyperlink or an attachment is counted and recorded, because a URL is not an artifact mints a deterministic (the source's own relation word) edge; an unresolved target enters as an external reference
System.State one value where the item sits in its type's workflow — a churn field, so it is recorded and queryable rather than in the body recorded as a queryable node property — changing it sends no episode
System.Reason one value why it last changed state, recorded recorded as a queryable node property — changing it sends no episode
System.Tags one value the item's tags, split on ADO's semicolon and recorded as a list recorded as a queryable node property — changing it sends no episode
System.AreaPath one value the area path — a churn field, recorded recorded as a queryable node property — changing it sends no episode
System.IterationPath one value the iteration path — a churn field, recorded recorded as a queryable node property — changing it sends no episode
System.AssignedTo one value who it is assigned to, recorded as assignee with ADO's display name recorded as a queryable node property — changing it sends no episode
Microsoft.VSTS.Common.Priority one value priority — a churn field, recorded recorded as a queryable node property — changing it sends no episode
Microsoft.VSTS.Scheduling.StoryPoints one value story points (or Effort, whichever the process declares) — a churn field, recorded recorded as a queryable node property — changing it sends no episode
System.Rev one value the work item's revision number, recorded — and the token the write adapter tests against so a revision approved on stale content cannot overwrite somebody else's edit recorded as a queryable node property — changing it sends no episode
System.CreatedDate one value when ADO says the work item was created — source_created_at, never Revetly's clock content
System.ChangedDate one value when ADO says it last changed — source_updated_at. Not what the change signal reads: a query over this field cannot see a work item that is no longer there, which is why the signal is the revisions feed's own cursor content
cited_sources a list the artifacts that informed this one, read back out of the description's ## Revetly section where the write half puts them — this store has no field of its own for a citation. They land as the artifact's own CITES edges and never leak into the body the reviewer approved mints a deterministic cites edge; an unresolved target is dropped
supersedes a list the artifacts this one replaces, read back out of the same ## Revetly section — a work item that replaced another says so where this store gives it nowhere else to say it mints a deterministic supersedes edge; an unresolved target is dropped
System.CommentCount one value how many comments the work item has — what decides whether its thread is worth a request at all, and truthful here, unlike the equivalent field on a Todoist task recorded as a queryable node property — changing it sends no episode
reporting revision one value one revision of one work item off the reporting feed. It becomes a revision artifact when it moved a field named in history_fields, and nothing at all when it did not — which is what keeps a decade affordable. Immutable: extracted once, gated on identity, never re-billed content
revision state before / after one value what System.State moved from and to, recorded on the revision rather than written into its body — a transition is a fact to query, not prose to re-extract recorded as a queryable node property — changing it sends no episode
comment.text one value the comment's own text, the whole body of a comment artifact. History, and the one kind of it this source lets somebody change, so it is gated on its bytes and an edit costs one episode rides the episode body — changing it re-pays extraction
comment.createdDate one value when the comment was posted — the moment the artifact lands at, never Revetly's clock and never the moment it was read content
comment version one value what a comment used to say, read only when comment_versions is on because it is a request per comment. Lands as an immutable comment-version artifact keyed by comment id and version content

commits connector

Not file-backed: each commit's message, hash, author and author date — never the patch, and never the list of paths it touched. revetly check reports this declaration rather than parsing frontmatter that does not exist.

Unbounded snapshot: this source has no current state an ordinary pass can read whole — its history is its snapshot. Each repository is therefore read forward from the moment it was connected to this instance, or from the floor declared for it, and what is older reaches CONTEXT only through revetly backfill, priced per window. revetly doctor reports the floor in force per repository and where it came from.

Settings — [connectors.commits]

Which repositories this instance reads commits from, how far back, and which id families a commit message is scanned for.

key type default accepts what it is
repos list of str required The repositories whose commits are read, relative to the project folder.
since table empty How far back an ordinary read pass goes, per repository.
id_families list of str EPIC, FEAT, STORY, PBI The id families a commit message is scanned for.

A key not listed above is kept and read by nothing — never dropped and never refused, because the engine rules on no key it was not told about.

Declares no keys: every file under its roots is read whole, and revetly check reports this source as unchecked rather than as clean. Nothing here is optional because nothing here is read.

conversations connector

Settings — [connectors.conversations]

Declares no settings: whatever this table holds is carried to the connector unread, and revetly check reports it as unchecked rather than as clean. A key inside it is this connector's own business.

key shape what it does effect
id one value this note's key in CONTEXT; the filename when absent content
type one value what kind of note this is, carried as the document type (defaults to conversation-note) content
captured_at one value when the exchange happened — the note's own account, which beats git history for a note committed some time after the conversation content
same_as a list the same record's id in another source mints a deterministic same-as edge; an unresolved target enters as an external reference
supersedes a list the notes this one overtakes — a later decision replacing an earlier one, so the earlier reads as the reasoning that was binding rather than as what binds now mints a deterministic supersedes edge; an unresolved target is dropped
revises id, or {id, criteria, criteria_fp} the work items this decision changes in part, each a bare id or {id, criteria: [1, 3], criteria_fp: [...]} naming the acceptance criteria it changes by 1-based position — so a read of the item says it was revised, and by which note, while the rest of it still holds mints a deterministic revises edge; an unresolved target is dropped
related a list ids this note relates to; record_conversation also writes them into the body, which is where the explicit-reference pass finds them and links the ones that are work items or decision notes — the frontmatter key itself is not read not read into CONTEXT
decisions a list the decision notes split out of this conversation, likewise carried in the body rather than read from here not read into CONTEXT
conversation one value the conversation a decision note came out of not read into CONTEXT
captured_via one value which tool wrote the note, for the audit trail not read into CONTEXT
attribution one value how far this record's own account of who wrote it can be trusted — verified where an issuer's keys checked the caller's claims, self-asserted where the name came off the process, absent where nothing has characterised it — one of: unknown, self-asserted, verified content
attribution_issuer one value which issuer checked those claims, where the label is verified and the token named one content
attribution_actor one value whether the name on this record is a person's or a machine's — machine where a token this instance declared authenticated it, absent where the store does not distinguish the two — one of: machine, person content
participants a list who was in the exchange not read into CONTEXT

deliveries connector

Settings — [connectors.deliveries]

Declares no settings: whatever this table holds is carried to the connector unread, and revetly check reports it as unchecked rather than as clean. A key inside it is this connector's own business.

key shape what it does effect
id one value this record's key in CONTEXT; the filename, uppercased, when absent content
type one value what this record is — always a delivery record rides the episode body — changing it re-pays extraction
delivers one value the story this record reports a delivery of — the edge from what shipped back to what was asked for mints a deterministic delivers edge; an unresolved target enters as an external reference
criteria a list one entry per acceptance criterion the story was reviewed against, each carrying the criterion as written, a verdict of held or departed, and — when it departed — the stated reason and, if known, whether the departure was decided by the builder or the user rides the episode body, and is recorded as a node property
criteria_declared one value false when the store of record declares no acceptance criteria at all — a different fact from a delivery that ruled on none, and the reason the record is neither refused nor given a fabricated list rides the episode body, and is recorded as a node property
departures a list departures stated against a work item with no declared criteria — each carrying its reason and, if known, who decided it; a criterion-less departure has no numbered line to hang on and is stated by itself rides the episode body, and is recorded as a node property
shipped one value what shipped, as stated — the account of the delivery for a work item whose store declares no criteria to rule on rides the episode body — changing it re-pays extraction
held_by one value the system that holds the work item this record reports on, when it is not this instance; absent means the work item is this instance's own rides the episode body, and is recorded as a node property
tracker_note one value true when a delivery note was posted on the tracker item for this record; absent means the record predates the note and no comment is expected of it recorded as a queryable node property — changing it sends no episode
delivered_at one value when this delivery happened, which is what makes a backlog's delivery history land as history rather than as one ingest day recorded as a queryable node property — changing it sends no episode
delivered_by one value who shipped it rides the episode body, and is recorded as a node property
evidence one value what proves it shipped — the implementing commit sha, PR, or test run rides the episode body, and is recorded as a node property
implemented_by a list commits or pull requests that implemented the story; they stay references CONTEXT points at, never artifacts it ingests. An abbreviated commit sha links to the one commit in the project whose full sha starts with it mints a deterministic implemented-by edge; an unresolved target enters as an external reference
cited_sources a list the decision notes and artifacts that reasoned about this delivery; entries may be a bare id or ID (path/to/source.md) mints a deterministic cites edge; an unresolved target enters as an external reference
attribution one value how far this record's own account of who wrote it can be trusted — verified where an issuer's keys checked the caller's claims, self-asserted where the name came off the process, absent where nothing has characterised it — one of: unknown, self-asserted, verified content
attribution_issuer one value which issuer checked those claims, where the label is verified and the token named one content
attribution_actor one value whether the name on this record is a person's or a machine's — machine where a token this instance declared authenticated it, absent where the store does not distinguish the two — one of: machine, person content

docs connector

Settings — [connectors.docs]

Declares no settings: whatever this table holds is carried to the connector unread, and revetly check reports it as unchecked rather than as clean. A key inside it is this connector's own business.

Declares no keys: every file under its roots is read whole, and revetly check reports this source as unchecked rather than as clean. Nothing here is optional because nothing here is read.

github connector

Not file-backed: issues and pull requests from the GitHub REST API over each repository [connectors.github] declares, with pull-request closing references read over GraphQL. revetly check reports this declaration rather than parsing frontmatter that does not exist.

Settings — [connectors.github]

Which GitHub repositories this instance reads, and how their issue types and labels map onto this project's own.

Read by connector 'github' and write adapter 'github' — one table, one parse, so the two halves of this backend cannot be pointed at different places.

key type default accepts what it is
token_env str GITHUB_TOKEN Names the environment variable holding the personal access token.
repo str none — one of repo or repos is required, never both The repository whose issues are the backlog, as owner/name.
repos list of str none — repo is the one-item list Several repositories in place of repo; the first is the default.
types table empty GitHub's issue types or labels onto this project's item types.
states table merged over defaults: draft = open + label draft, ready = open, in-progress = open + label in-progress, done = closed/completed (or no reason), parked = open + label parked, discarded = closed/not_planned or duplicate This project's statuses onto GitHub's open/closed states.
comments bool true Whether an ordinary read pass reads comment threads.
comment_threads_per_pass int (threads) 0 How many comment threads one pass may fetch; zero means every.
pull_requests bool true Whether pull requests and their closing references are read.
base_url str https://api.github.com The host every GitHub call goes to, for GitHub Enterprise.
api_version str 2026-03-10 The X-GitHub-Api-Version every call names.
timeout float (seconds) 30.0 Seconds one API call may take before the read pass gives up.

A key not listed above is kept and read by nothing — never dropped and never refused, because the engine rules on no key it was not told about.

Connection: token_env, base_url, api_version, timeout. These establish communication with the tool; every other key above is scope. On an instance with more than one project, a project's own table that states none of them inherits exactly these from the instance's table, and one that states any of them states its own connection whole. Scope is never inherited.

key shape what it does effect
number one value GitHub's own key for the issue, which becomes this artifact's native_id — the graph id is github:<owner>/<repo>:<number> content
title one value the issue title, which becomes the artifact title and the heading of the episode body rides the episode body — changing it re-pays extraction
body one value the issue's markdown body: the description, the '## Acceptance criteria' list (bullets or numbered items) read by the shared parser, and the '## Revetly' trailer split into cited sources, supersedes and a Same as: claim rides the episode body — changing it re-pays extraction
type one value the native issue type, translated through [connectors.github.types]. A native type wins over labels; where the map names nothing the issue ingests as kind = "issue" recorded as a queryable node property — changing it sends no episode
labels one value the issue's labels, one of which may be the label:<name> key that maps it onto a PBI type; kept queryable recorded as a queryable node property — changing it sends no episode
parent_issue_url one value the parent edge, from GitHub's own native sub-issues and nowhere else mints a deterministic parent edge; an unresolved target enters as an external reference
html_url one value the address a human can open, carried as source_uri content
created_at one value when GitHub says the issue was created content
updated_at one value when GitHub says it last changed — the clock history windows tile on content
closingIssuesReferences one value the issues a pull request closes, read over GraphQL and carried as closes edges mints a deterministic closes edge; an unresolved target enters as an external reference
comment.body one value the comment's own text, the whole body of a comment artifact rides the episode body — changing it re-pays extraction
comment.created_at one value when the comment was posted content
timeline one value one issue timeline event — closed, reopened, labelled, unlabelled, renamed, an issue type or parent change — landing as an immutable transition artifact content
cited_sources a list the artifacts that informed this one, read back out of the description's ## Revetly section where the write half puts them — this store has no field of its own for a citation. They land as the artifact's own CITES edges and never leak into the body the reviewer approved mints a deterministic cites edge; an unresolved target is dropped
supersedes a list the artifacts this one replaces, read back out of the same ## Revetly section — a work item that replaced another says so where this store gives it nowhere else to say it mints a deterministic supersedes edge; an unresolved target is dropped
same_as a list the work item this one is the same as in another source, read back out of the description's ## Revetly section — the claim that lets an id an old store issued keep resolving to the artifact that replaced it mints a deterministic same-as edge; an unresolved target enters as an external reference

pm connector

Settings — [connectors.pm]

Declares no settings: whatever this table holds is carried to the connector unread, and revetly check reports it as unchecked rather than as clean. A key inside it is this connector's own business.

key shape what it does effect
id one value this artifact's key in CONTEXT; the filename, uppercased, when absent content
type one value which of this project's PBI types this work item is — how it is placed in the hierarchy and what parent validation applies — one of: epic, feature, story rides the episode body — changing it re-pays extraction
parent one value the artifact this one rolls up under — the only way the backlog's hierarchy reaches the graph mints a deterministic parent edge; an unresolved target enters as an external reference
status one value where this sits in the delivery lifecycle; the read surface filters on it, and discarded artifacts are hidden by default — one of: draft, ready, in-progress, done, parked, discarded recorded as a queryable node property — changing it sends no episode
owner one value who is accountable for the work item rides the episode body — changing it re-pays extraction
actors a list who this work item is about, as structured references — {native_id, role, display_name, email, same_as} entries. owner above keeps working and is read as one of these; declaring this is how a person is stated to be the same person as an id in another source content
business_rule one value the rule the work item enforces, as prose rides the episode body, and is recorded as a node property
acceptance_criteria a list what has to be true for the work item to be done rides the episode body, and is recorded as a node property
proposes one value what this artifact puts forward rides the episode body, and is recorded as a node property
supersedes a list artifacts this one replaces mints a deterministic supersedes edge; an unresolved target is dropped
updates one value artifacts this one amends without replacing rides the episode body, and is recorded as a node property
discard_reason one value why a discarded artifact will never be built; required when status is discarded, cleared on any other transition recorded as a queryable node property — changing it sends no episode
same_as a list the same work item's id in another source — a claim the source makes, never a guess from matching strings mints a deterministic same-as edge; an unresolved target enters as an external reference
recovered_from one value on a work item restored after a second approval wrote over its id: {approved_as, commit, reason} — the id it was first approved as and the commit that held its text. revetly check reads it to stop reporting that collision as a loss; approved_as answers when the commit is from a history since rewritten not read into CONTEXT
cited_sources a list the artifacts that informed this one; entries may be a bare id or ID (path/to/source.md) mints a deterministic cites edge; an unresolved target is dropped
edges type target, or {type, target(s)} relations this store states outright — blocks STORY-000, or {type: blocks, targets: [...]}; the type is your word, not the engine's mints a deterministic source-declared relation edge; an unresolved target enters as an external reference
open_questions a list the forks this work item was approved with still undecided — the questions a reviewer weighed rather than settled, kept so the next session reads them here rather than re-litigating them recorded as a queryable node property — changing it sends no episode
claim one value who has this work item in progress and since when — {by, at, reason, taken_over_from}, written by set_status … in-progress and cleared by any other transition recorded as a queryable node property — changing it sends no episode
review_log a list the approval and status-change record the review gate appends to — who approved this artifact, when, through which gesture, and every status transition it went through with the evidence that proved it shipped recorded as a queryable node property — changing it sends no episode
attribution one value how far this record's own account of who wrote it can be trusted — verified where an issuer's keys checked the caller's claims, self-asserted where the name came off the process, absent where nothing has characterised it — one of: unknown, self-asserted, verified content
attribution_issuer one value which issuer checked those claims, where the label is verified and the token named one content
attribution_actor one value whether the name on this record is a person's or a machine's — machine where a token this instance declared authenticated it, absent where the store does not distinguish the two — one of: machine, person content

todoist connector

Not file-backed: tasks from the Todoist API (api.todoist.com/api/v1), active and completed, with their comment threads, over the projects [connectors.todoist] scopes it to. revetly check reports this declaration rather than parsing frontmatter that does not exist.

Settings — [connectors.todoist]

Which Todoist account and projects this instance reads, and what one ordinary pass over them may spend.

Read by connector 'todoist' and write adapter 'todoist' — one table, one parse, so the two halves of this backend cannot be pointed at different places.

key type default accepts what it is
token_env str TODOIST_API_TOKEN Names the environment variable holding the Todoist API token, never the token.
project str empty The one Todoist project this instance reads and writes.
projects list of str empty Several Todoist projects to read, alongside a project for new tasks.
write_project str empty Which of several read projects receives new tasks.
account str empty The account half of every id this source mints.
base_url str https://api.todoist.com/api/v1 The API base URL every Todoist call goes to.
timeout float (seconds) 30.0 Seconds one API call may take before the read pass gives up.
page_size int (tasks per page) 200 How many tasks one request asks for; clamped to 200.
completed_lookback_days int (days) 365 How many days of the completion feed an ordinary read pass covers.
comments bool true Whether an ordinary read pass reads comment threads; off is a cost lever.
comment_threads_per_pass int (threads) 0 How many comment threads one read pass may fetch; zero means every thread.
activity_pages int (pages) 100 How many pages of the activity log a history read walks.

A key not listed above is kept and read by nothing — never dropped and never refused, because the engine rules on no key it was not told about.

Connection: token_env, base_url, timeout. These establish communication with the tool; every other key above is scope. On an instance with more than one project, a project's own table that states none of them inherits exactly these from the instance's table, and one that states any of them states its own connection whole. Scope is never inherited.

key shape what it does effect
id one value Todoist's own key for the task, which becomes this artifact's native_id — the graph id is todoist:<account>:<id> content
content one value the task's one-line content, which becomes the artifact title and the heading of the episode body rides the episode body — changing it re-pays extraction
description one value the task's long description, the only other field that rides the episode body — so extraction re-runs when the ticket's text changes and not when its scheduling does rides the episode body — changing it re-pays extraction
parent_id one value the task this one is a subtask of — a deterministic parent edge, never an LLM guess mints a deterministic parent edge; an unresolved target enters as an external reference
checked one value whether the task is completed, which is the status this artifact carries; a completed task is read from the completed endpoint rather than being allowed to look deleted — one of: open, done recorded as a queryable node property — changing it sends no episode
completed_at one value when the task was completed, recorded rather than bodied recorded as a queryable node property — changing it sends no episode
project_id one value the project the task sits in; also what project (or projects) in [connectors.todoist] scopes the read to. Recorded with the project's name, not turned into hierarchy recorded as a queryable node property — changing it sends no episode
section_id one value the section the task sits in, recorded with the section's name recorded as a queryable node property — changing it sends no episode
priority one value 1 (normal) to 4 (urgent) — a churn field, so it is recorded and queryable rather than in the body recorded as a queryable node property — changing it sends no episode
child_order one value position among sibling tasks — a churn field, recorded recorded as a queryable node property — changing it sends no episode
labels one value label names on the task — a churn field, recorded recorded as a queryable node property — changing it sends no episode
due one value due date, its human string and whether it recurs — churn fields, recorded as due_date / due_string / due_is_recurring recorded as a queryable node property — changing it sends no episode
deadline one value the task's deadline date, recorded recorded as a queryable node property — changing it sends no episode
responsible_uid one value who the task is assigned to, as Todoist's user id — recorded as assignee_uid, because resolving it to a name is a second API surface this MVP does not read recorded as a queryable node property — changing it sends no episode
cited_sources a list the artifacts that informed this one, read back out of the description's ## Revetly section where the write half puts them — this store has no field of its own for a citation. They land as the artifact's own CITES edges and never leak into the body the reviewer approved mints a deterministic cites edge; an unresolved target is dropped
supersedes a list the artifacts this one replaces, read back out of the same ## Revetly section — a work item that replaced another says so where this store gives it nowhere else to say it mints a deterministic supersedes edge; an unresolved target is dropped
added_at one value when Todoist says the task was created — source_created_at, never Revetly's clock content
updated_at one value when Todoist says the task last changed — source_updated_at, left unset when the API does not carry one rather than filled in with Revetly's clock content
note_count one value how many comments the task has, which is what decides whether its thread is fetched at all — a task with none costs no request recorded as a queryable node property — changing it sends no episode
comment.content one value the comment's text, which is the whole body of a comment artifact (kind=comment). History, and the one kind of it this source lets people change — so it is gated on its bytes rather than on its identity, because an edit moves this field and nothing else. Read on the ordinary pass as well as the historical one, so a comment deleted in Todoist tombstones under the ordinary rules rather than living forever rides the episode body — changing it re-pays extraction
comment.posted_at one value when the comment was posted — the moment the artifact lands at, never the time it was read content
comment.item_id / comment.project_id one value the task or project the comment hangs under — a deterministic comment-on edge, minted from structured data with no provider call spent on the anchor mints a deterministic comment-on edge; an unresolved target enters as an external reference
completed_task.completed_at one value a completion filed against a task, read from the completion-date feed and landed as its own immutable artifact (kind=completion) at that instant. Read on BOTH passes: the ordinary one walks the same feed over completed_lookback_days in any case, and reading it as records rather than only as tasks is what lets a completion the source has withdrawn — somebody reopened the task — stop being asserted in CONTEXT content
activity.event_type one value what the activity log says happened — added, updated, deleted, uncompleted and the rest. completed is skipped, because the completion feed already owns it and has no plan horizon on it recorded as a queryable node property — changing it sends no episode
activity.event_date one value when the logged event happened. Historical pass only, and only as far back as the account's plan retains the log. The newest one in scope is also this connector's change signal: one GET says whether the account moved at all, and nothing the feed returns narrows what the pass it triggers then reads content
activity.object_id one value the task, project or comment the event happened to — a deterministic transition-of edge mints a deterministic transition-of edge; an unresolved target enters as an external reference

A key not listed above is not read: it reaches CONTEXT only if the extractor happens to pick it out of the prose, with no guarantee it does and no edge type if it does.