Add actionable roadmap and agent guidance
This commit is contained in:
parent
f302342b1c
commit
b892dc6165
2 changed files with 392 additions and 48 deletions
109
AGENTS.md
Normal file
109
AGENTS.md
Normal file
|
|
@ -0,0 +1,109 @@
|
||||||
|
# AGENTS.md
|
||||||
|
|
||||||
|
This repository is `geth`, a Rust monorepo for a personal, local-first,
|
||||||
|
Iroh-based mesh runtime. It is not the Ethereum geth client.
|
||||||
|
|
||||||
|
`geth` has one executable only:
|
||||||
|
|
||||||
|
- daemon mode: `geth daemon run`
|
||||||
|
- control mode: `geth status`, `geth node ...`, `geth cas ...`, etc.
|
||||||
|
|
||||||
|
Do not add separate `gethd` or `gethctl` binaries.
|
||||||
|
|
||||||
|
## Core Invariants
|
||||||
|
|
||||||
|
- All remote geth node-to-node communication must be over Iroh.
|
||||||
|
- SSH is not a geth transport and must not become a fallback transport.
|
||||||
|
- SSH keys are admin trust anchors and ecosystem integration points.
|
||||||
|
- SSH certificate flows and SSH proxying are geth-managed workflows carried by
|
||||||
|
local control now and Iroh later.
|
||||||
|
- The daemon owns the Iroh endpoint, local identity, local metadata store,
|
||||||
|
resource registry, module routing, and synchronized data structures.
|
||||||
|
- Discovery is untrusted and never grants capabilities or trust.
|
||||||
|
- Authorization is resource-scoped and capability-based.
|
||||||
|
- Signed data must use deterministic canonical encoding, not arbitrary JSON.
|
||||||
|
- Bearer secrets may grant resource-specific access but must not create trusted
|
||||||
|
node identity or trust graph mutation rights.
|
||||||
|
|
||||||
|
## Workspace Shape
|
||||||
|
|
||||||
|
- Binary crate: `crates/geth`
|
||||||
|
- CLI definitions and formatting: `crates/geth-cli`
|
||||||
|
- Daemon orchestration and local control: `crates/geth-node`
|
||||||
|
- Local JSONL control protocol: `crates/geth-control`
|
||||||
|
- Shared newtypes: `crates/geth-types`
|
||||||
|
- Canonical encoding/hash helpers: `crates/geth-codec`
|
||||||
|
- Config/path handling: `crates/geth-config`
|
||||||
|
- Local SQLite metadata store: `crates/geth-store`
|
||||||
|
- Local CAS: `crates/geth-cas`
|
||||||
|
- SSH trust/cert models: `crates/geth-ssh-identity`
|
||||||
|
|
||||||
|
Keep crates focused. Do not create a `geth-common` crate.
|
||||||
|
|
||||||
|
## Implementation Rules
|
||||||
|
|
||||||
|
- Prefer existing crate boundaries and patterns.
|
||||||
|
- Libraries should use `thiserror`; binaries may use `anyhow`.
|
||||||
|
- Keep async only where it is useful.
|
||||||
|
- Treat network and control inputs as untrusted.
|
||||||
|
- Do not implement complex cryptographic protocols casually. Add types,
|
||||||
|
roadmap notes, and explicit non-claims when a feature is only scaffolded.
|
||||||
|
- Do not claim Keyhive/BeeKEM-level security properties until they are actually
|
||||||
|
designed, implemented, and tested.
|
||||||
|
- User service installation must target user service managers, not system
|
||||||
|
service managers.
|
||||||
|
|
||||||
|
## Testing Requirements
|
||||||
|
|
||||||
|
Every behavior change should include tests proportional to risk:
|
||||||
|
|
||||||
|
- Add unit tests for pure data models, reducers, encoders, path generation, and
|
||||||
|
command builders.
|
||||||
|
- Add integration tests for CLI/daemon behavior when practical.
|
||||||
|
- Use temp directories and temp `GETH_HOME` values.
|
||||||
|
- Avoid tests that require privileged services, real YubiKeys, real network
|
||||||
|
relays, or host service-manager mutation.
|
||||||
|
- For platform-specific behavior, use generated-definition tests or cfg guards.
|
||||||
|
|
||||||
|
Before finishing a code change, run at least:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
cargo fmt --all -- --check
|
||||||
|
cargo check --workspace --all-targets
|
||||||
|
cargo test --workspace
|
||||||
|
```
|
||||||
|
|
||||||
|
Also run clippy when reasonable:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
cargo clippy --workspace --all-targets -- -D warnings
|
||||||
|
```
|
||||||
|
|
||||||
|
If a command cannot be run, state why in the final report.
|
||||||
|
|
||||||
|
## Documentation And Roadmap
|
||||||
|
|
||||||
|
Keep documentation updated with behavior changes:
|
||||||
|
|
||||||
|
- Update `README.md` for user-visible commands or workflows.
|
||||||
|
- Update `docs/architecture.md` for architectural boundaries or invariants.
|
||||||
|
- Add or update ADRs in `docs/adr/` for durable decisions.
|
||||||
|
- Update `docs/roadmap.md` whenever a feature is added, completed, split, or
|
||||||
|
deferred.
|
||||||
|
|
||||||
|
Roadmap items should be actionable and checkable:
|
||||||
|
|
||||||
|
- Use `[ ]`, `[~]`, or `[x]`.
|
||||||
|
- Include acceptance criteria.
|
||||||
|
- Split vague items before implementation.
|
||||||
|
- Do not mark an item done unless the acceptance criteria are met or revised
|
||||||
|
honestly.
|
||||||
|
|
||||||
|
## Current Feature Boundaries
|
||||||
|
|
||||||
|
- Local daemon, local control socket, local identity, local store, local CAS,
|
||||||
|
SSH certificate metadata, revocation metadata, and user service definitions
|
||||||
|
exist.
|
||||||
|
- Iroh, cr-sqlite, iroh-docs, iroh-gossip, iroh-blobs, Automerge sync, real auth
|
||||||
|
enforcement, OpenSSH KRL generation, and Keyhive/BeeKEM-style authorization are
|
||||||
|
future roadmap items unless implemented later.
|
||||||
323
docs/roadmap.md
323
docs/roadmap.md
|
|
@ -1,64 +1,299 @@
|
||||||
# Roadmap
|
# Roadmap
|
||||||
|
|
||||||
|
This roadmap is intended to be checkable. Each item should either be completed
|
||||||
|
with tests/docs or split into smaller items before implementation.
|
||||||
|
|
||||||
|
Status markers:
|
||||||
|
|
||||||
|
- `[ ]` Not started
|
||||||
|
- `[~]` In progress
|
||||||
|
- `[x]` Done
|
||||||
|
|
||||||
## Phase 0: Bootstrap
|
## Phase 0: Bootstrap
|
||||||
|
|
||||||
- single CLI
|
Goal: establish a compiling single-binary local runtime with local state, local
|
||||||
- local daemon
|
control, local CAS, service installation, and written architecture decisions.
|
||||||
- local store
|
|
||||||
- local identity
|
- `[x]` Single `geth` binary with daemon and control modes.
|
||||||
- local CAS
|
Acceptance criteria:
|
||||||
- user service installer for systemd, launchd, and Windows scheduled tasks
|
- `cargo run -p geth -- init` initializes a temp `GETH_HOME`.
|
||||||
- docs and ADRs
|
- `cargo run -p geth -- daemon run` starts one local daemon.
|
||||||
|
- No `gethd` or `gethctl` binaries exist in the workspace.
|
||||||
|
|
||||||
|
- `[x]` Local daemon control socket.
|
||||||
|
Acceptance criteria:
|
||||||
|
- Control request/response types roundtrip through JSONL serialization.
|
||||||
|
- `geth status` and `geth node id` talk to a running daemon.
|
||||||
|
- Control decoding treats input as untrusted and returns structured errors.
|
||||||
|
|
||||||
|
- `[x]` Local metadata store and identity.
|
||||||
|
Acceptance criteria:
|
||||||
|
- SQLite migrations are idempotent.
|
||||||
|
- Agent identity persists across repeated opens of the same `GETH_HOME`.
|
||||||
|
- Store schema includes resources, auth/keychain logs, CAS, peers, modules,
|
||||||
|
SSH cert requests, certificates, and revocations.
|
||||||
|
|
||||||
|
- `[x]` Local filesystem CAS.
|
||||||
|
Acceptance criteria:
|
||||||
|
- `geth cas add/hash/has/get/list` work through the daemon.
|
||||||
|
- Blob paths use BLAKE3 hashes under `cas/blobs/<aa>/<bb>/<hash>`.
|
||||||
|
- Unit or integration tests verify bytes roundtrip unchanged.
|
||||||
|
|
||||||
|
- `[x]` User service installer.
|
||||||
|
Acceptance criteria:
|
||||||
|
- `geth daemon service print --manager systemd|launchd|windows-task` prints
|
||||||
|
the expected user-level service definition or command.
|
||||||
|
- Linux install targets a systemd user unit, not a system service.
|
||||||
|
- macOS install targets a launchd user agent.
|
||||||
|
- Windows install targets a per-user scheduled task.
|
||||||
|
- Tests verify generated definitions do not target privileged system services.
|
||||||
|
|
||||||
|
- `[x]` Bootstrap docs and ADRs.
|
||||||
|
Acceptance criteria:
|
||||||
|
- README explains what geth is and what it is not.
|
||||||
|
- Architecture docs state Iroh-only remote communication and SSH trust role.
|
||||||
|
- ADRs cover single binary, Iroh-only transport, resources, auth, SSH, CAS,
|
||||||
|
synchronized structures, service installation, and SSH certificate flows.
|
||||||
|
|
||||||
## Phase 1: Iroh Foundation
|
## Phase 1: Iroh Foundation
|
||||||
|
|
||||||
- Iroh endpoint
|
Goal: make the daemon own a real Iroh endpoint and establish authenticated
|
||||||
- peer cards
|
geth-to-geth connections without granting trust from discovery alone.
|
||||||
- peer discovery
|
|
||||||
- peer exchange
|
- `[ ]` Pin and compile Iroh dependencies.
|
||||||
- basic authenticated peer connection
|
Acceptance criteria:
|
||||||
- additional user service manager backends where useful
|
- `iroh` is added only after APIs are pinned and `cargo check --workspace`
|
||||||
|
passes.
|
||||||
|
- `geth-iroh` exposes endpoint startup/shutdown wrappers.
|
||||||
|
- Docs note exact crate versions and any API assumptions.
|
||||||
|
|
||||||
|
- `[ ]` Daemon-owned Iroh endpoint.
|
||||||
|
Acceptance criteria:
|
||||||
|
- The daemon creates one shared Iroh endpoint on startup.
|
||||||
|
- `geth status --json` reports endpoint status and EndpointID.
|
||||||
|
- Endpoint identity is bound to agent/node identity in local metadata.
|
||||||
|
- Restarting the daemon preserves higher-level node identity.
|
||||||
|
|
||||||
|
- `[ ]` Protocol/router scaffold.
|
||||||
|
Acceptance criteria:
|
||||||
|
- ALPN constants are registered through one module router.
|
||||||
|
- Unknown ALPNs are rejected explicitly.
|
||||||
|
- Tests cover registration collisions and unknown protocol handling.
|
||||||
|
|
||||||
|
- `[ ]` Peer cards.
|
||||||
|
Acceptance criteria:
|
||||||
|
- A peer card contains node ID, agent ID, endpoint candidates, timestamp, and
|
||||||
|
signature metadata.
|
||||||
|
- Peer cards are stored in `peer_cards`.
|
||||||
|
- Invalid or unsigned peer cards do not update trust state.
|
||||||
|
|
||||||
|
- `[ ]` Untrusted discovery backend trait.
|
||||||
|
Acceptance criteria:
|
||||||
|
- Discovery returns candidate peer cards only.
|
||||||
|
- No discovery result grants capabilities or trust.
|
||||||
|
- `auth explain` can distinguish "discovered" from "trusted".
|
||||||
|
|
||||||
|
- `[ ]` Basic authenticated peer connection.
|
||||||
|
Acceptance criteria:
|
||||||
|
- A node can dial another node over Iroh using an EndpointID from a peer card.
|
||||||
|
- The remote side proves an agent/node binding before module access.
|
||||||
|
- Knowing only an EndpointID is insufficient to access a protected module.
|
||||||
|
|
||||||
## Phase 2: Trust And Authorization
|
## Phase 2: Trust And Authorization
|
||||||
|
|
||||||
- SSH-admin-rooted keychain
|
Goal: replace stubs with signed, reducible keychain/auth operation logs and
|
||||||
- SSH certificate request and renewal queues
|
resource-scoped capability decisions.
|
||||||
- CA/YubiKey approval workflow
|
|
||||||
- certificate and key revocation-list records
|
|
||||||
- signed keychain ops
|
|
||||||
- resource auth ops
|
|
||||||
- grants and revocations
|
|
||||||
- auth explain
|
|
||||||
- resource secrets
|
|
||||||
- bearer invites
|
|
||||||
|
|
||||||
## Phase 3: CAS/KV/Pubsub
|
- `[ ]` Canonical signed operation envelope.
|
||||||
|
Acceptance criteria:
|
||||||
|
- Keychain and auth ops use deterministic canonical encoding for signatures.
|
||||||
|
- JSON is not used as the signed representation.
|
||||||
|
- Tests verify equivalent operations hash/sign identically across runs.
|
||||||
|
|
||||||
- iroh-blobs CAS
|
- `[ ]` SSH-admin-rooted keychain initialization.
|
||||||
- iroh-docs KV
|
Acceptance criteria:
|
||||||
- iroh-gossip pubsub
|
- `geth keychain init` records a signed `KeychainInit`.
|
||||||
- private topics and encrypted values
|
- OpenSSH signature namespaces are explicit.
|
||||||
|
- Missing `ssh-keygen` or unavailable hardware keys produce clear errors.
|
||||||
|
|
||||||
## Phase 4: Pipes And SSH Proxy
|
- `[ ]` Keychain operation reducer.
|
||||||
|
Acceptance criteria:
|
||||||
|
- Admin keys, users, devices, nodes, agents, and endpoint bindings reduce into
|
||||||
|
a current keychain view.
|
||||||
|
- Revoked keys/devices/nodes are excluded from active views.
|
||||||
|
- Tests cover add, rename, revoke, and endpoint rotation.
|
||||||
|
|
||||||
- dumbpipe-style streams
|
- `[ ]` Resource auth operation reducer.
|
||||||
- TCP and Unix socket forwarding
|
Acceptance criteria:
|
||||||
- SSH proxy over Iroh
|
- Resource create, authority set, grants, revocations, and groups reduce into
|
||||||
- SSH certificate and revocation distribution over Iroh
|
a current permission view.
|
||||||
- OpenSSH KRL export/import support
|
- Capabilities are strings, including scoped forms such as
|
||||||
- restricted geth admin shell
|
`kv.write_prefix:apps/foo/`.
|
||||||
|
- Tests cover grant, revoke, group membership, and denied access.
|
||||||
|
|
||||||
|
- `[ ]` `auth explain` real decision path.
|
||||||
|
Acceptance criteria:
|
||||||
|
- `geth auth explain <subject> <resource> <capability>` reports allowed/denied.
|
||||||
|
- Output includes the operation chain or missing grant that caused the result.
|
||||||
|
- JSON output is stable enough for tests and scripts.
|
||||||
|
|
||||||
|
- `[ ]` Resource secrets and bearer invites.
|
||||||
|
Acceptance criteria:
|
||||||
|
- Bearer secrets grant only resource-scoped capabilities.
|
||||||
|
- Bearer principals cannot mutate trust graph state by default.
|
||||||
|
- Secret epoch rotation is represented in durable metadata.
|
||||||
|
- Tests verify bearer access does not imply node identity.
|
||||||
|
|
||||||
|
- `[~]` SSH certificate and revocation lifecycle.
|
||||||
|
Acceptance criteria:
|
||||||
|
- `geth ssh cert request/requests/approve/import/list` persist local metadata.
|
||||||
|
- Approval emits an explicit `ssh-keygen -s ...` command for CA/YubiKey use.
|
||||||
|
- `geth ssh revocation add/list/export` persists and exports revocations.
|
||||||
|
- Future completion requires auth checks for request, approve, import, publish,
|
||||||
|
and read capabilities.
|
||||||
|
|
||||||
|
## Phase 3: CAS, KV, And Pubsub
|
||||||
|
|
||||||
|
Goal: turn local CAS and stubs into Iroh-backed replicated modules while keeping
|
||||||
|
authorization and durable-state boundaries clear.
|
||||||
|
|
||||||
|
- `[ ]` Iroh-blobs CAS integration.
|
||||||
|
Acceptance criteria:
|
||||||
|
- Local CAS can provide and fetch blobs over Iroh.
|
||||||
|
- Provider tracking is recorded locally.
|
||||||
|
- Local add/get/hash/has/list behavior remains backward compatible.
|
||||||
|
|
||||||
|
- `[ ]` CAS pin and cache policy.
|
||||||
|
Acceptance criteria:
|
||||||
|
- Pinned blobs are retained across cache cleanup.
|
||||||
|
- Unpinned cached blobs can be evicted by policy.
|
||||||
|
- Tests cover pin, unpin, list, and attempted eviction of pinned data.
|
||||||
|
|
||||||
|
- `[ ]` Encrypted private blobs.
|
||||||
|
Acceptance criteria:
|
||||||
|
- Private blob payloads are encrypted before network distribution.
|
||||||
|
- Access is gated by resource secret epoch material.
|
||||||
|
- Docs explicitly avoid claiming forward secrecy or PCS.
|
||||||
|
|
||||||
|
- `[ ]` Iroh-docs KV integration.
|
||||||
|
Acceptance criteria:
|
||||||
|
- `geth kv create/set/get` works against a named KV resource.
|
||||||
|
- Prefix-scoped capabilities can allow or deny writes.
|
||||||
|
- KV metadata is durable and replicated through Iroh Documents.
|
||||||
|
|
||||||
|
- `[ ]` Iroh-gossip pubsub integration.
|
||||||
|
Acceptance criteria:
|
||||||
|
- `geth pubsub pub/sub` works for local test nodes.
|
||||||
|
- Pubsub messages are lossy notifications, not durable facts.
|
||||||
|
- Docs and tests keep durable state in CAS/KV/document/db instead.
|
||||||
|
|
||||||
|
## Phase 4: Pipes, SSH Proxy, And SSH Distribution
|
||||||
|
|
||||||
|
Goal: add authorized stream-oriented management workflows over Iroh.
|
||||||
|
|
||||||
|
- `[ ]` Dumbpipe-style Iroh streams.
|
||||||
|
Acceptance criteria:
|
||||||
|
- `geth pipe listen/connect` can connect two local test nodes.
|
||||||
|
- Pipe access requires `pipe.listen` or `pipe.connect`.
|
||||||
|
- Streams close cleanly and propagate errors.
|
||||||
|
|
||||||
|
- `[ ]` TCP forwarding.
|
||||||
|
Acceptance criteria:
|
||||||
|
- A local TCP listener can forward over an authorized Iroh pipe.
|
||||||
|
- Tests cover basic request/response forwarding.
|
||||||
|
- Forwarding is resource-scoped and can be disabled by auth.
|
||||||
|
|
||||||
|
- `[ ]` Unix socket forwarding where supported.
|
||||||
|
Acceptance criteria:
|
||||||
|
- Unix socket forwarding is available on Unix platforms.
|
||||||
|
- Unsupported platforms return clear errors.
|
||||||
|
- Tests skip or use cfg guards where sockets are unavailable.
|
||||||
|
|
||||||
|
- `[ ]` SSH proxy over Iroh.
|
||||||
|
Acceptance criteria:
|
||||||
|
- `geth ssh proxy <node>` opens an authorized Iroh stream.
|
||||||
|
- Remote daemon checks `ssh_proxy.connect` before connecting to local sshd or
|
||||||
|
admin shell.
|
||||||
|
- Knowing an EndpointID alone cannot reach sshd.
|
||||||
|
|
||||||
|
- `[ ]` SSH certificate and revocation distribution.
|
||||||
|
Acceptance criteria:
|
||||||
|
- Issued cert records and revocation records replicate over Iroh.
|
||||||
|
- Consumers can list current certs/revocations from local state while offline.
|
||||||
|
- Conflicting or unsigned records are rejected or quarantined.
|
||||||
|
|
||||||
|
- `[ ]` OpenSSH KRL import/export.
|
||||||
|
Acceptance criteria:
|
||||||
|
- Revocation records can produce an OpenSSH KRL file.
|
||||||
|
- Existing KRL files can be imported into revocation metadata where possible.
|
||||||
|
- Tests cover serial, key ID, public key, and certificate revocations.
|
||||||
|
|
||||||
## Phase 5: DB And Documents
|
## Phase 5: DB And Documents
|
||||||
|
|
||||||
- cr-sqlite sync
|
Goal: add durable synchronized data structures for SQLite/cr-sqlite and
|
||||||
- Automerge sync
|
Automerge documents.
|
||||||
- resource-attached authorization
|
|
||||||
- secret-derived encryption
|
- `[ ]` DB resource registration.
|
||||||
|
Acceptance criteria:
|
||||||
|
- `geth db add <name> <path>` records DB metadata.
|
||||||
|
- `geth db status <name>` reports local path, schema metadata, and sync state.
|
||||||
|
- Nonexistent paths and invalid names produce clear errors.
|
||||||
|
|
||||||
|
- `[ ]` cr-sqlite change extraction.
|
||||||
|
Acceptance criteria:
|
||||||
|
- The module can read changes from `crsql_changes`.
|
||||||
|
- Schema hash/version metadata is included in sync batches.
|
||||||
|
- Tests use a temp SQLite DB and deterministic fixture changes.
|
||||||
|
|
||||||
|
- `[ ]` DB sync over Iroh.
|
||||||
|
Acceptance criteria:
|
||||||
|
- Two local test nodes can exchange and apply DB changes.
|
||||||
|
- Schema mismatch is detected before applying changes.
|
||||||
|
- Optional CAS-backed snapshots or batches are documented if used.
|
||||||
|
|
||||||
|
- `[ ]` Automerge document resource.
|
||||||
|
Acceptance criteria:
|
||||||
|
- `geth document create/status` works for local documents.
|
||||||
|
- Document state is stored durably.
|
||||||
|
- Tests cover create, update, save, and reload.
|
||||||
|
|
||||||
|
- `[ ]` Automerge sync over Iroh.
|
||||||
|
Acceptance criteria:
|
||||||
|
- Two local test nodes can synchronize document changes.
|
||||||
|
- Resource authorization gates read/write sync.
|
||||||
|
- Conflicts converge according to Automerge semantics.
|
||||||
|
|
||||||
## Phase 6: File Sync And Advanced Local-First Auth
|
## Phase 6: File Sync And Advanced Local-First Auth
|
||||||
|
|
||||||
- CAS tree objects
|
Goal: build higher-level local-first collaboration on CAS trees, resource auth,
|
||||||
- file roots
|
and future group key evolution.
|
||||||
- conflict handling
|
|
||||||
- Keyhive-like convergent capabilities
|
- `[ ]` CAS tree objects.
|
||||||
- BeeKEM/CGKA-inspired group key evolution
|
Acceptance criteria:
|
||||||
|
- Tree objects describe directories, files, executable bits, and blob hashes.
|
||||||
|
- Tree objects are content-addressed and stored in CAS.
|
||||||
|
- Tests cover deterministic tree hashing.
|
||||||
|
|
||||||
|
- `[ ]` File roots.
|
||||||
|
Acceptance criteria:
|
||||||
|
- A file root maps a local path to a CAS tree resource.
|
||||||
|
- Scan detects create/update/delete/rename changes.
|
||||||
|
- Sync never silently overwrites local changes without a recorded decision.
|
||||||
|
|
||||||
|
- `[ ]` Conflict handling.
|
||||||
|
Acceptance criteria:
|
||||||
|
- Conflicts are represented as durable metadata.
|
||||||
|
- CLI can list conflicts and choose a resolution.
|
||||||
|
- Tests cover concurrent edit, delete/edit, and rename conflicts.
|
||||||
|
|
||||||
|
- `[ ]` Keyhive-like convergent capabilities.
|
||||||
|
Acceptance criteria:
|
||||||
|
- Capability state is represented as local-first replicated auth data.
|
||||||
|
- Convergent grant/revoke semantics are documented and tested.
|
||||||
|
- Migration from current auth ops is documented.
|
||||||
|
|
||||||
|
- `[ ]` BeeKEM/CGKA-inspired group key evolution.
|
||||||
|
Acceptance criteria:
|
||||||
|
- Design doc states exact security properties and non-properties.
|
||||||
|
- Prototype is behind explicit experimental module boundaries.
|
||||||
|
- Current resource secret epoch model remains compatible or has a migration.
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue