Skip to content

Commit 1610949

Browse files
committed
feat(cli): add local tenant store subscribe
1 parent e8d34ae commit 1610949

14 files changed

Lines changed: 340 additions & 41 deletions

File tree

README.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,15 @@ FINCRAWL_AGE_IDENTITY=<age-identity> \
7979
fincrawl import --in snapshots/local.jsonl.zst.age --dry-run
8080
```
8181

82+
For a local tenant-controlled store with a `manifest.json`, verify and import
83+
the listed encrypted JSONL snapshots in one step:
84+
85+
```bash
86+
fincrawl store verify <tenant-store-root>
87+
FINCRAWL_AGE_IDENTITY=<age-identity> \
88+
fincrawl subscribe <tenant-store-root> --dry-run
89+
```
90+
8291
Plaintext archive output is local scratch data only. Tenant encrypted snapshots
8392
still belong in tenant-controlled private storage, not in this repository.
8493

@@ -115,6 +124,7 @@ Common flows:
115124
| Export encrypted snapshot | `fincrawl publish --recipient <recipient> --out snapshots/local.jsonl.zst.age` |
116125
| Import encrypted snapshot | `fincrawl import --identity <identity> --in snapshots/local.jsonl.zst.age` |
117126
| Verify an encrypted tenant store | `fincrawl store verify <path>` |
127+
| Import a local tenant store | `fincrawl subscribe <path> --identity <identity>` |
118128
| Check repo guardrails | `fincrawl guard --json` |
119129

120130
## Docs

docs/architecture.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ fincrawl archive --fixture testdata/synthetic --recipient <age-recipient> --out
3131
fincrawl publish --recipient <age-recipient> --out snapshots/local.jsonl.zst.age
3232
fincrawl import --identity <age-identity> --in snapshots/local.jsonl.zst.age
3333
fincrawl store verify <tenant-store-root>
34+
fincrawl subscribe <tenant-store-root> --dry-run
3435
fincrawl guard --json
3536
```
3637

@@ -116,6 +117,11 @@ verifier reads `manifest.json`, requires manifest snapshots to point at existing
116117
compressed age-encrypted artifacts, and rejects plaintext archives, SQLite
117118
stores, runtime state, logs, reports, screenshots, and transcripts.
118119

120+
`fincrawl subscribe <path>` is a local one-shot subscriber flow. It verifies a
121+
tenant-controlled store, reads the manifest, and imports listed
122+
`.jsonl.zst.age` snapshots into local SQLite. It does not clone, pull, push,
123+
schedule jobs, or persist tenant-specific subscription config.
124+
119125
## Agent Use
120126

121127
Agents should load [the repo skill](../skills/fincrawl/SKILL.md) before using a

docs/roadmap.md

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,19 +25,21 @@ this repo.
2525
- zstd + age encrypted `archive`, `publish`, and `import` flows.
2626
- Generic `store verify` checks for tenant-controlled encrypted snapshot
2727
manifests.
28+
- Local one-shot `subscribe` imports `.jsonl.zst.age` snapshots from a verified
29+
tenant store path.
2830
- Repository guardrails for plaintext archives, generated artifacts, secret
2931
patterns, provider URLs, and transcript-like files.
3032
- Agent-facing skill guidance under `skills/fincrawl/`.
3133
- CI/release automation for `0.0.x` bootstrap releases with protected `main`.
3234

3335
## Next
3436

35-
- Add a read-only subscriber flow that can pull/import a local tenant store
36-
path without adding remote push or schedule mechanics.
3737
- Broaden exact hydration/search ergonomics around known provider URLs while
3838
keeping provider IDs path-safe.
3939
- Add more sync torture coverage around interrupted multi-page windows and
4040
repeated transient failures.
41+
- Add persisted local subscription metadata only if repeated local store imports
42+
need stale checks or operator reminders.
4143

4244
## Later
4345

docs/tenant-data-boundary.md

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -87,10 +87,17 @@ existing compressed age-encrypted snapshots with relative paths and rejects
8787
plaintext archives, local databases, runtime state, logs, reports, screenshots,
8888
and transcripts.
8989

90+
Use `fincrawl subscribe <tenant-store-root>` only for local one-shot imports from
91+
a tenant-controlled store. The command verifies the store before import and
92+
hydrates local SQLite from encrypted JSONL snapshots; it does not make the
93+
generic repo a home for tenant store state, schedules, or remote credentials.
94+
9095
Encryption recipients may be native `age1...` recipients or SSH public keys
9196
accepted by age. Private age identities and private SSH keys must stay outside
92-
the repo. Import dry-runs still need a private decrypt identity because they
93-
validate encrypted record contents and counts.
97+
the repo. `import --dry-run` still needs a private decrypt identity because it
98+
validates encrypted record contents and counts. `subscribe --dry-run` verifies
99+
the store manifest and planned snapshots without decrypting or mutating local
100+
SQLite.
94101

95102
## Fixture Rule
96103

internal/cli/agent_dx.go

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -238,6 +238,25 @@ func describeCommands(command string) (cliSchema, error) {
238238
"Input paths must be relative, stay under the current working directory, and end in .jsonl.zst.age.",
239239
},
240240
},
241+
"subscribe": {
242+
Name: "subscribe",
243+
Summary: "Import snapshots from a local encrypted tenant store.",
244+
Mutates: true,
245+
JSON: true,
246+
Args: []paramSchema{
247+
{Name: "store", Type: "path", Required: true, Help: "Local tenant store root containing manifest.json."},
248+
},
249+
Flags: []paramSchema{
250+
{Name: "identity", Type: "age-identity", Help: "Age identity. Defaults to FINCRAWL_AGE_IDENTITY."},
251+
{Name: "dry-run", Type: "bool", Help: "Verify and list planned imports without writing local state."},
252+
{Name: "json", Type: "bool", Default: "true", Help: "Print JSON output."},
253+
},
254+
Examples: []string{"fincrawl subscribe ../tenant-store --dry-run", "fincrawl subscribe ../tenant-store --identity AGE-SECRET-KEY-..."},
255+
Notes: []string{
256+
"Local filesystem stores only; remote clone, pull, and scheduling are out of scope.",
257+
"The tenant store is verified before import, and this command currently imports .jsonl.zst.age snapshots.",
258+
},
259+
},
241260
"store verify": {
242261
Name: "store verify",
243262
Summary: "Verify a generic encrypted tenant-store manifest and artifact boundary.",

internal/cli/cli.go

Lines changed: 125 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -26,19 +26,20 @@ import (
2626
)
2727

2828
type app struct {
29-
Doctor doctorCmd `cmd:"" help:"Check local configuration."`
30-
Metadata metadataCmd `cmd:"" help:"Print machine-readable metadata."`
31-
Describe describeCmd `cmd:"" help:"Print machine-readable command schemas."`
32-
Status statusCmd `cmd:"" help:"Print local archive status."`
33-
Sync syncCmd `cmd:"" help:"Sync conversations from fixtures or provider APIs."`
34-
Search searchCmd `cmd:"" help:"Search the local archive."`
35-
Show showCmd `cmd:"" help:"Show one local conversation."`
36-
Archive archiveCmd `cmd:"" help:"Write compressed age-encrypted archive output."`
37-
Publish publishCmd `cmd:"" help:"Publish local SQLite state as an encrypted snapshot."`
38-
Import importCmd `cmd:"" help:"Import an encrypted snapshot into local SQLite."`
39-
Store storeCmd `cmd:"" help:"Inspect generic tenant-store contracts."`
40-
Guard guardCmd `cmd:"" help:"Check commit guardrails."`
41-
Version versionCmd `cmd:"" help:"Print version information."`
29+
Doctor doctorCmd `cmd:"" help:"Check local configuration."`
30+
Metadata metadataCmd `cmd:"" help:"Print machine-readable metadata."`
31+
Describe describeCmd `cmd:"" help:"Print machine-readable command schemas."`
32+
Status statusCmd `cmd:"" help:"Print local archive status."`
33+
Sync syncCmd `cmd:"" help:"Sync conversations from fixtures or provider APIs."`
34+
Search searchCmd `cmd:"" help:"Search the local archive."`
35+
Show showCmd `cmd:"" help:"Show one local conversation."`
36+
Archive archiveCmd `cmd:"" help:"Write compressed age-encrypted archive output."`
37+
Publish publishCmd `cmd:"" help:"Publish local SQLite state as an encrypted snapshot."`
38+
Import importCmd `cmd:"" help:"Import an encrypted snapshot into local SQLite."`
39+
Subscribe subscribeCmd `cmd:"" help:"Import snapshots from a local encrypted tenant store."`
40+
Store storeCmd `cmd:"" help:"Inspect generic tenant-store contracts."`
41+
Guard guardCmd `cmd:"" help:"Check commit guardrails."`
42+
Version versionCmd `cmd:"" help:"Print version information."`
4243
}
4344

4445
type commandContext struct {
@@ -633,6 +634,117 @@ func (cmd importCmd) Run(ctx commandContext) error {
633634
return writeMaybeJSON(ctx.stdout, cmd.JSON, importResult{Input: cmd.In, Records: len(records), Sync: result})
634635
}
635636

637+
type subscribeCmd struct {
638+
Store string `arg:"" help:"Local tenant store root containing manifest.json."`
639+
Identity string `help:"Age identity. Defaults to FINCRAWL_AGE_IDENTITY."`
640+
DryRun bool `name:"dry-run" help:"Verify and list planned imports without writing local state."`
641+
JSON bool `help:"Print JSON output." default:"true"`
642+
}
643+
644+
type subscribeResult struct {
645+
Store string `json:"store"`
646+
DryRun bool `json:"dry_run,omitempty"`
647+
Snapshots []subscribeSnapshot `json:"snapshots"`
648+
ImportedSnapshots int `json:"imported_snapshots"`
649+
Records int `json:"records"`
650+
Sync *store.SyncResult `json:"sync,omitempty"`
651+
}
652+
653+
type subscribeSnapshot struct {
654+
Path string `json:"path"`
655+
Records int `json:"records,omitempty"`
656+
Imported bool `json:"imported,omitempty"`
657+
}
658+
659+
type pendingSnapshotImport struct {
660+
snapshot tenantstore.SnapshotFile
661+
records []archive.Record
662+
fixture store.Fixture
663+
}
664+
665+
func (cmd subscribeCmd) Run(ctx commandContext) error {
666+
rt, err := config.LoadRuntime()
667+
if err != nil {
668+
return err
669+
}
670+
report, snapshots, err := tenantstore.VerifiedSnapshots(ctx, cmd.Store)
671+
if err != nil {
672+
return err
673+
}
674+
if !report.OK {
675+
if writeErr := writeMaybeJSON(ctx.stdout, cmd.JSON, report); writeErr != nil {
676+
return writeErr
677+
}
678+
return fmt.Errorf("tenant store verification failed with %d finding(s)", len(report.Findings))
679+
}
680+
result := subscribeResult{Store: report.Root, DryRun: cmd.DryRun, Snapshots: make([]subscribeSnapshot, 0, len(snapshots))}
681+
for _, snapshot := range snapshots {
682+
if !strings.HasSuffix(snapshot.Path, ".jsonl.zst.age") {
683+
return fmt.Errorf("subscribe currently imports only .jsonl.zst.age snapshots: %s", snapshot.Path)
684+
}
685+
result.Snapshots = append(result.Snapshots, subscribeSnapshot{Path: snapshot.Path, Records: snapshot.Records})
686+
result.Records += snapshot.Records
687+
}
688+
if cmd.DryRun {
689+
return writeMaybeJSON(ctx.stdout, cmd.JSON, result)
690+
}
691+
identity := strings.TrimSpace(cmd.Identity)
692+
if identity == "" {
693+
identity = config.AgeIdentity()
694+
}
695+
pending := make([]pendingSnapshotImport, 0, len(snapshots))
696+
totalRecords := 0
697+
for _, snapshot := range snapshots {
698+
records, err := archive.ReadEncryptedJSONL(snapshot.FullPath, identity)
699+
if err != nil {
700+
return fmt.Errorf("read tenant store snapshot %s: %w", snapshot.Path, err)
701+
}
702+
fixture, err := archive.RecordsFixture(records)
703+
if err != nil {
704+
return fmt.Errorf("decode tenant store snapshot %s: %w", snapshot.Path, err)
705+
}
706+
pending = append(pending, pendingSnapshotImport{snapshot: snapshot, records: records, fixture: fixture})
707+
totalRecords += len(records)
708+
}
709+
if err := config.EnsureDirs(rt); err != nil {
710+
return err
711+
}
712+
lck, err := lock.Acquire(rt.Config.DBPath)
713+
if err != nil {
714+
return err
715+
}
716+
defer lck.Release()
717+
aggregate := store.SyncResult{}
718+
for index, item := range pending {
719+
syncResult, err := store.SyncFixture(ctx, rt.Config.DBPath, item.fixture)
720+
if err != nil {
721+
return fmt.Errorf("import tenant store snapshot %s: %w", item.snapshot.Path, err)
722+
}
723+
mergeSyncResult(&aggregate, syncResult)
724+
result.Snapshots[index].Records = len(item.records)
725+
result.Snapshots[index].Imported = true
726+
result.Snapshots[index].Path = item.snapshot.Path
727+
result.ImportedSnapshots++
728+
}
729+
result.Records = totalRecords
730+
result.Sync = &aggregate
731+
return writeMaybeJSON(ctx.stdout, cmd.JSON, result)
732+
}
733+
734+
func mergeSyncResult(dst *store.SyncResult, src store.SyncResult) {
735+
if dst.WorkspaceID == "" {
736+
dst.WorkspaceID = src.WorkspaceID
737+
}
738+
dst.Conversations += src.Conversations
739+
dst.ConversationParts += src.ConversationParts
740+
dst.Admins += src.Admins
741+
dst.Teams += src.Teams
742+
dst.Tags += src.Tags
743+
dst.Contacts += src.Contacts
744+
dst.RawBlobs += src.RawBlobs
745+
dst.Warnings = append(dst.Warnings, src.Warnings...)
746+
}
747+
636748
type storeCmd struct {
637749
Verify storeVerifyCmd `cmd:"" help:"Verify an encrypted tenant-store manifest."`
638750
}

internal/cli/cli_test.go

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -707,6 +707,96 @@ func TestPublishImportEncryptedSnapshotRoundTrip(t *testing.T) {
707707
}
708708
}
709709

710+
func TestSubscribeDryRunVerifiesLocalStoreWithoutIdentity(t *testing.T) {
711+
storeRoot := filepath.Join(t.TempDir(), "store")
712+
if err := os.MkdirAll(filepath.Join(storeRoot, "snapshots"), 0o755); err != nil {
713+
t.Fatal(err)
714+
}
715+
if err := os.WriteFile(filepath.Join(storeRoot, "snapshots", "synthetic.jsonl.zst.age"), []byte("encrypted placeholder"), 0o600); err != nil {
716+
t.Fatal(err)
717+
}
718+
if err := os.WriteFile(filepath.Join(storeRoot, "manifest.json"), []byte(`{"snapshots":[{"path":" snapshots/synthetic.jsonl.zst.age ","records":13}]}`), 0o600); err != nil {
719+
t.Fatal(err)
720+
}
721+
t.Setenv("FINCRAWL_HOME", t.TempDir())
722+
var stdout bytes.Buffer
723+
var stderr bytes.Buffer
724+
725+
if err := Run(context.Background(), []string{"subscribe", storeRoot, "--dry-run", "--json"}, &stdout, &stderr); err != nil {
726+
t.Fatal(err)
727+
}
728+
var result struct {
729+
DryRun bool `json:"dry_run"`
730+
ImportedSnapshots int `json:"imported_snapshots"`
731+
Records int `json:"records"`
732+
Snapshots []struct {
733+
Path string `json:"path"`
734+
Records int `json:"records"`
735+
} `json:"snapshots"`
736+
}
737+
if err := json.Unmarshal(stdout.Bytes(), &result); err != nil {
738+
t.Fatal(err)
739+
}
740+
if !result.DryRun || result.ImportedSnapshots != 0 || result.Records != 13 || len(result.Snapshots) != 1 {
741+
t.Fatalf("result = %#v", result)
742+
}
743+
if result.Snapshots[0].Path != "snapshots/synthetic.jsonl.zst.age" {
744+
t.Fatalf("snapshot path = %q", result.Snapshots[0].Path)
745+
}
746+
}
747+
748+
func TestSubscribeImportsLocalTenantStore(t *testing.T) {
749+
sourceHome := t.TempDir()
750+
targetHome := t.TempDir()
751+
t.Setenv("FINCRAWL_HOME", sourceHome)
752+
identity, err := age.GenerateX25519Identity()
753+
if err != nil {
754+
t.Fatal(err)
755+
}
756+
storeRoot := filepath.Join("tmp", fmt.Sprintf("subscribe-store-%d", time.Now().UnixNano()))
757+
out := filepath.Join(storeRoot, "snapshots", "synthetic.jsonl.zst.age")
758+
t.Cleanup(func() {
759+
_ = os.RemoveAll(storeRoot)
760+
})
761+
var stdout bytes.Buffer
762+
var stderr bytes.Buffer
763+
764+
if err := Run(context.Background(), []string{"sync", "--fixture", filepath.Join("..", "..", "testdata", "synthetic")}, &stdout, &stderr); err != nil {
765+
t.Fatal(err)
766+
}
767+
stdout.Reset()
768+
if err := Run(context.Background(), []string{"publish", "--recipient", identity.Recipient().String(), "--out", out}, &stdout, &stderr); err != nil {
769+
t.Fatal(err)
770+
}
771+
if err := os.WriteFile(filepath.Join(storeRoot, "manifest.json"), []byte(`{"snapshots":[{"path":"snapshots/synthetic.jsonl.zst.age"}]}`), 0o600); err != nil {
772+
t.Fatal(err)
773+
}
774+
stdout.Reset()
775+
if err := os.Setenv("FINCRAWL_HOME", targetHome); err != nil {
776+
t.Fatal(err)
777+
}
778+
if err := Run(context.Background(), []string{"subscribe", storeRoot, "--identity", identity.String(), "--json"}, &stdout, &stderr); err != nil {
779+
t.Fatal(err)
780+
}
781+
var result struct {
782+
ImportedSnapshots int `json:"imported_snapshots"`
783+
Records int `json:"records"`
784+
}
785+
if err := json.Unmarshal(stdout.Bytes(), &result); err != nil {
786+
t.Fatal(err)
787+
}
788+
if result.ImportedSnapshots != 1 || result.Records == 0 {
789+
t.Fatalf("result = %#v", result)
790+
}
791+
stdout.Reset()
792+
if err := Run(context.Background(), []string{"search", "Morgan", "--fields", "provider_id,subject", "--json"}, &stdout, &stderr); err != nil {
793+
t.Fatal(err)
794+
}
795+
if !bytes.Contains(stdout.Bytes(), []byte(`"provider_id": "ic_syn_002"`)) {
796+
t.Fatalf("stdout = %q", stdout.String())
797+
}
798+
}
799+
710800
func TestParseSinceAcceptsDayDurations(t *testing.T) {
711801
now := time.Date(2026, 5, 16, 12, 0, 0, 0, time.UTC)
712802
got, err := parseSince("2d", now, "updated-since")

internal/control/control.go

Lines changed: 14 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -43,20 +43,21 @@ func Manifest(rt config.Runtime) ckcontrol.Manifest {
4343
DefaultShare: rt.Config.ShareDir,
4444
}
4545
manifest.Commands = map[string]ckcontrol.Command{
46-
"doctor": {Title: "Check local configuration", Argv: []string{"fincrawl", "doctor", "--offline"}, JSON: true},
47-
"metadata": {Title: "Print machine-readable metadata", Argv: []string{"fincrawl", "metadata"}, JSON: true},
48-
"describe": {Title: "Print command schemas", Argv: []string{"fincrawl", "describe"}, JSON: true},
49-
"status": {Title: "Print archive status", Argv: []string{"fincrawl", "status"}, JSON: true},
50-
"sync": {Title: "Sync conversations", Argv: []string{"fincrawl", "sync", "--fixture", "testdata/synthetic", "--dry-run"}, JSON: true, Mutates: true},
51-
"search": {Title: "Search local archive", Argv: []string{"fincrawl", "search", "query", "--fields", "provider_id,subject,score,updated_at"}, JSON: true},
52-
"show": {Title: "Show one local conversation", Argv: []string{"fincrawl", "show", "provider-conversation-id", "--fields", "provider_id,subject,tags,snippet"}, JSON: true},
53-
"archive": {Title: "Write encrypted archive", Argv: []string{"fincrawl", "archive", "--fixture", "testdata/synthetic", "--recipient", syntheticArchiveRecipient, "--out", "tmp/fincrawl-smoke.jsonl.zst.age", "--dry-run"}, JSON: true, Mutates: true},
54-
"publish": {Title: "Publish encrypted snapshot", Argv: []string{"fincrawl", "publish", "--recipient", syntheticArchiveRecipient, "--out", "tmp/fincrawl-publish.jsonl.zst.age", "--dry-run"}, JSON: true, Mutates: true},
55-
"import": {Title: "Import encrypted snapshot", Argv: []string{"fincrawl", "import", "--in", "tmp/fincrawl-publish.jsonl.zst.age", "--dry-run"}, JSON: true, Mutates: true},
56-
"store": {Title: "Verify encrypted tenant store", Argv: []string{"fincrawl", "store", "verify", "."}, JSON: true},
57-
"guard": {Title: "Check commit guardrails", Argv: []string{"fincrawl", "guard"}, JSON: true},
46+
"doctor": {Title: "Check local configuration", Argv: []string{"fincrawl", "doctor", "--offline"}, JSON: true},
47+
"metadata": {Title: "Print machine-readable metadata", Argv: []string{"fincrawl", "metadata"}, JSON: true},
48+
"describe": {Title: "Print command schemas", Argv: []string{"fincrawl", "describe"}, JSON: true},
49+
"status": {Title: "Print archive status", Argv: []string{"fincrawl", "status"}, JSON: true},
50+
"sync": {Title: "Sync conversations", Argv: []string{"fincrawl", "sync", "--fixture", "testdata/synthetic", "--dry-run"}, JSON: true, Mutates: true},
51+
"search": {Title: "Search local archive", Argv: []string{"fincrawl", "search", "query", "--fields", "provider_id,subject,score,updated_at"}, JSON: true},
52+
"show": {Title: "Show one local conversation", Argv: []string{"fincrawl", "show", "provider-conversation-id", "--fields", "provider_id,subject,tags,snippet"}, JSON: true},
53+
"archive": {Title: "Write encrypted archive", Argv: []string{"fincrawl", "archive", "--fixture", "testdata/synthetic", "--recipient", syntheticArchiveRecipient, "--out", "tmp/fincrawl-smoke.jsonl.zst.age", "--dry-run"}, JSON: true, Mutates: true},
54+
"publish": {Title: "Publish encrypted snapshot", Argv: []string{"fincrawl", "publish", "--recipient", syntheticArchiveRecipient, "--out", "tmp/fincrawl-publish.jsonl.zst.age", "--dry-run"}, JSON: true, Mutates: true},
55+
"import": {Title: "Import encrypted snapshot", Argv: []string{"fincrawl", "import", "--in", "tmp/fincrawl-publish.jsonl.zst.age", "--dry-run"}, JSON: true, Mutates: true},
56+
"subscribe": {Title: "Import local tenant store snapshots", Argv: []string{"fincrawl", "subscribe", "tenant-store", "--dry-run"}, JSON: true, Mutates: true},
57+
"store": {Title: "Verify encrypted tenant store", Argv: []string{"fincrawl", "store", "verify", "."}, JSON: true},
58+
"guard": {Title: "Check commit guardrails", Argv: []string{"fincrawl", "guard"}, JSON: true},
5859
}
59-
manifest.Capabilities = []string{"intercom-api-sync", "sqlite-fts-search", "conversation-show", "zstd-age-archive", "encrypted-snapshot-import", "tenant-store-verify", "synthetic-fixtures"}
60+
manifest.Capabilities = []string{"intercom-api-sync", "sqlite-fts-search", "conversation-show", "zstd-age-archive", "encrypted-snapshot-import", "local-tenant-store-subscribe", "tenant-store-verify", "synthetic-fixtures"}
6061
manifest.Privacy = ckcontrol.Privacy{
6162
ContainsPrivateMessages: true,
6263
ExportsSecrets: false,

0 commit comments

Comments
 (0)