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.
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.
revetly ingest, and the watch loop's tick — does both. That is
the point of it: under the shape this engine is deployed in it is on nobody's
laptop, so a teammate's commit reaches CONTEXT only if something pulls, and a
repository read for the first time has the moment it was connected written
down. [git] fetch = false is how an instance that is genuinely local-only
opts out of the first half.revetly check, revetly ingest --dry-run and the configuration page's quote each read every checkout at the
revision it already holds, name that revision as held, and write no
connection date — a unit that has never been connected is reported as one a
pass would connect, and the date is left for the pass that connects it.
Each of them says so where it prints its findings, beside the line saying
nothing was billed.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.
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.
provenance — one of ai-derived, human-authored, mirror, source-of-recordcontent_format — one of json, markdown, message, textThree 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.
authority — one of inferred, asserted, declared. How strongly the source claims this. Defaulted
to asserted, which is the reading that is never wrong about a source
that said nothing: somebody stated it, and nothing here vouches for it. A store
whose records go through a gate declares declared and outranks a passing
remark.confidence — a number on 0–1 where the source states one, and None where it
does not. Left unset it stays unset: a confidence nobody stated must never be
readable as certainty, so normalise your source's own scale in the
connector, which is the only code that knows what its 87 was out of.sensitivity — one of public, internal, confidential, restricted, least to most restricted. Defaulted to
internal. Anything above internal is elevated: the
redaction and consent pass reports it whether or not a pattern matched, and
[egress] withhold_above keeps it from crossing to the provider at all. That is
the point of declaring it — a customer ticket is confidential because of what it
is, not because of what happens to be written in it.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.
attribution — one of unknown, self-asserted, verified, defaulted to unknown.
How far the record's own account of who wrote it can be trusted. Not
authority, which is how strongly the source claims the record: two axes that
would sort the same way and mean two different facts. Declare verified
only where an issuer's keys actually checked the caller's claims, and name that
issuer in attribution_issuer; declare self-asserted where the name came
off the process, the environment or a config file. Say nothing and it reads as
unknown — which is the honest answer for a source that has no
idea how its records were attributed, and is never read as the weak value: an
absent stamp is not evidence of a weak one.attribution_issuer — who checked those claims. Refused beside any label but
verified, because "self-asserted, issued by login.acme.com" reads as a
check nobody made.attribution_actor — machine where the name on the record belongs to one,
and absent where the store does not distinguish a machine from a person. A
second axis and not a third value on the first: a machine presenting a token
the instance declared and a person presenting one their issuer signed are
equally checked, so verified stays one question and this answers the
other. Absent is the ordinary case, which is what makes its presence the
answer.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 |
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:
state-transitions, field-revisions, comments, commits this source can answer forrevetly 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) |
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 connectorNot 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.
[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 connectorNot 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.
[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[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[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[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 connectorNot 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.
[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[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 connectorNot 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.
[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.