@hraness/peopleblade 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,87 @@
1
+ PRAGMA foreign_keys = ON;
2
+
3
+ CREATE UNIQUE INDEX identity_one_root_per_pair
4
+ ON identity_decisions(left_person_id, right_person_id)
5
+ WHERE prior_decision_id IS NULL;
6
+
7
+ CREATE TRIGGER identity_decisions_legal_transition
8
+ BEFORE INSERT ON identity_decisions FOR EACH ROW
9
+ BEGIN
10
+ SELECT CASE WHEN NEW.prior_decision_id IS NULL AND NEW.action = 'separate'
11
+ THEN RAISE(ABORT, 'identity separation requires a current accepted decision') END;
12
+ SELECT CASE WHEN NEW.prior_decision_id IS NOT NULL AND EXISTS (
13
+ SELECT 1 FROM identity_current_decisions prior
14
+ WHERE prior.id = NEW.prior_decision_id
15
+ AND (
16
+ (prior.action = 'accept' AND NEW.action <> 'separate')
17
+ OR (prior.action IN ('reject','defer') AND (
18
+ NEW.action = 'separate' OR NEW.evidence_sha256 = prior.evidence_sha256
19
+ ))
20
+ OR (prior.action = 'separate' AND (
21
+ NEW.action = 'separate'
22
+ OR NEW.evidence_sha256 = coalesce(
23
+ json_extract(prior.evidence_json, '$.acceptedEvidenceSha256'),
24
+ prior.evidence_sha256
25
+ )
26
+ ))
27
+ )
28
+ ) THEN RAISE(ABORT, 'illegal identity decision transition') END;
29
+ END;
30
+
31
+ DROP VIEW person_identity_components;
32
+
33
+ CREATE VIEW person_identity_components AS
34
+ WITH RECURSIVE
35
+ accepted(left_person_id, right_person_id) AS (
36
+ SELECT left_person_id, right_person_id
37
+ FROM identity_current_decisions
38
+ WHERE action = 'accept'
39
+ ),
40
+ reach(person_id, member_id) AS (
41
+ SELECT id, id FROM people
42
+ UNION
43
+ SELECT reach.person_id,
44
+ CASE WHEN accepted.left_person_id = reach.member_id
45
+ THEN accepted.right_person_id ELSE accepted.left_person_id END
46
+ FROM reach
47
+ JOIN accepted
48
+ ON accepted.left_person_id = reach.member_id OR accepted.right_person_id = reach.member_id
49
+ ),
50
+ raw_components(person_id, raw_canonical_person_id) AS (
51
+ SELECT person_id, min(member_id)
52
+ FROM reach
53
+ GROUP BY person_id
54
+ ),
55
+ -- A reviewed target is durable representative evidence. Superseding the edge may
56
+ -- split the graph, but must not change the representative of any component that
57
+ -- still contains that richer target.
58
+ observed_targets(raw_canonical_person_id, target_person_id, decision_id) AS (
59
+ SELECT target.raw_canonical_person_id,
60
+ CAST(json_extract(decision.evidence_json, '$.reviewedTarget.anchorPersonId') AS INTEGER),
61
+ decision.id
62
+ FROM identity_decisions decision
63
+ JOIN raw_components target ON target.person_id = CAST(
64
+ json_extract(decision.evidence_json, '$.reviewedTarget.anchorPersonId') AS INTEGER
65
+ )
66
+ WHERE decision.action = 'accept'
67
+ AND json_extract(decision.evidence_json, '$.version') = 2
68
+ AND json_extract(decision.evidence_json, '$.kind') IN ('observed-email','observed-phone')
69
+ ),
70
+ component_ids(raw_canonical_person_id) AS (
71
+ SELECT DISTINCT raw_canonical_person_id FROM raw_components
72
+ ),
73
+ component_representatives(raw_canonical_person_id, canonical_person_id) AS (
74
+ SELECT component.raw_canonical_person_id,
75
+ coalesce((
76
+ SELECT target.target_person_id
77
+ FROM observed_targets target
78
+ WHERE target.raw_canonical_person_id = component.raw_canonical_person_id
79
+ ORDER BY target.decision_id
80
+ LIMIT 1
81
+ ), component.raw_canonical_person_id)
82
+ FROM component_ids component
83
+ )
84
+ SELECT raw.person_id, representative.canonical_person_id
85
+ FROM raw_components raw
86
+ JOIN component_representatives representative
87
+ ON representative.raw_canonical_person_id = raw.raw_canonical_person_id;
@@ -0,0 +1,15 @@
1
+ ALTER TABLE provider_resources
2
+ ADD COLUMN profile_url_identity_eligible INTEGER NOT NULL DEFAULT 0
3
+ CHECK(profile_url_identity_eligible IN (0,1));
4
+
5
+ UPDATE provider_resources
6
+ SET profile_url_identity_eligible = 1
7
+ WHERE provider IN ('instagram','linkedin')
8
+ AND profile_url IS NOT NULL
9
+ AND trim(profile_url) <> '';
10
+
11
+ CREATE TRIGGER provider_resources_profile_url_identity_update
12
+ AFTER UPDATE OF profile_url_identity_eligible ON provider_resources FOR EACH ROW
13
+ BEGIN
14
+ UPDATE local_revisions SET identity_revision = identity_revision + 1 WHERE singleton = 1;
15
+ END;
@@ -0,0 +1,39 @@
1
+ PRAGMA foreign_keys = ON;
2
+
3
+ -- Dated CRM notes. people.notes remains a legacy free-text blob and is not queried.
4
+ CREATE TABLE person_notes (
5
+ id INTEGER PRIMARY KEY,
6
+ person_id INTEGER NOT NULL REFERENCES people(id) ON DELETE RESTRICT,
7
+ occurred_at TEXT NOT NULL CHECK(length(occurred_at) BETWEEN 20 AND 64),
8
+ source TEXT NOT NULL CHECK(length(source) BETWEEN 1 AND 64 AND source GLOB '[a-z][a-z0-9-]*'),
9
+ source_id TEXT CHECK(source_id IS NULL OR (length(source_id) BETWEEN 1 AND 256)),
10
+ title TEXT CHECK(title IS NULL OR length(title) BETWEEN 1 AND 1024),
11
+ body TEXT NOT NULL CHECK(length(body) BETWEEN 1 AND 65536),
12
+ metadata_json TEXT NOT NULL DEFAULT '{}' CHECK(json_valid(metadata_json)),
13
+ created_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP
14
+ ) STRICT;
15
+ CREATE INDEX person_notes_person_occurred ON person_notes(person_id, occurred_at DESC, id);
16
+ CREATE INDEX person_notes_occurred ON person_notes(occurred_at, id);
17
+ CREATE INDEX person_notes_source ON person_notes(source, occurred_at, id);
18
+ CREATE UNIQUE INDEX person_notes_source_identity
19
+ ON person_notes(source, source_id, person_id)
20
+ WHERE source_id IS NOT NULL;
21
+
22
+ CREATE VIRTUAL TABLE person_notes_fts USING fts5(
23
+ title,
24
+ body,
25
+ content='person_notes',
26
+ content_rowid='id',
27
+ tokenize='unicode61 remove_diacritics 2'
28
+ );
29
+
30
+ CREATE TRIGGER person_notes_fts_insert AFTER INSERT ON person_notes FOR EACH ROW
31
+ BEGIN
32
+ INSERT INTO person_notes_fts(rowid, title, body)
33
+ VALUES (NEW.id, coalesce(NEW.title, ''), NEW.body);
34
+ END;
35
+
36
+ CREATE TRIGGER person_notes_immutable_update BEFORE UPDATE ON person_notes FOR EACH ROW
37
+ BEGIN SELECT RAISE(ABORT, 'person notes are append-only'); END;
38
+ CREATE TRIGGER person_notes_immutable_delete BEFORE DELETE ON person_notes FOR EACH ROW
39
+ BEGIN SELECT RAISE(ABORT, 'person notes are append-only'); END;
@@ -0,0 +1,33 @@
1
+ PRAGMA foreign_keys = ON;
2
+
3
+ -- Name matching is a separate identity capability from retaining a provider's
4
+ -- display metadata. New and unknown resource kinds fail closed until their
5
+ -- ingester explicitly opts the resource's names into automatic identity use.
6
+ ALTER TABLE provider_resources
7
+ ADD COLUMN name_identity_eligible INTEGER NOT NULL DEFAULT 0
8
+ CHECK(name_identity_eligible IN (0,1));
9
+
10
+ -- Preserve the established trust contract for existing user-maintained address
11
+ -- books, both bounded exact Google contact collections, and reviewed LinkedIn
12
+ -- profiles. Review-only sources such as Beeper retain the fail-closed default.
13
+ UPDATE provider_resources
14
+ SET name_identity_eligible = 1
15
+ WHERE provider = 'apple'
16
+ OR provider = 'linkedin'
17
+ OR (
18
+ provider IN ('google','google-contacts')
19
+ AND resource_type IN ('contacts','other-contacts')
20
+ );
21
+
22
+ -- The policy backfill changes the identity-search surface. Publish that change
23
+ -- exactly once; the steady-state row trigger below intentionally does not exist
24
+ -- while the historical rows are updated.
25
+ UPDATE local_revisions
26
+ SET identity_revision = identity_revision + 1
27
+ WHERE singleton = 1;
28
+
29
+ CREATE TRIGGER provider_resources_name_identity_update
30
+ AFTER UPDATE OF name_identity_eligible ON provider_resources FOR EACH ROW
31
+ BEGIN
32
+ UPDATE local_revisions SET identity_revision = identity_revision + 1 WHERE singleton = 1;
33
+ END;
@@ -0,0 +1,23 @@
1
+ PRAGMA foreign_keys = ON;
2
+
3
+ -- A complete provider collection is fetched before its import transaction.
4
+ -- Retain the exact materialized projection and observation time so an older
5
+ -- fetch cannot overwrite newer local state after waiting for the writer lock.
6
+ CREATE TABLE provider_collection_states (
7
+ provider TEXT NOT NULL CHECK(length(provider) BETWEEN 1 AND 64),
8
+ account_key TEXT NOT NULL COLLATE NOCASE CHECK(length(account_key) BETWEEN 1 AND 256),
9
+ collection_key TEXT NOT NULL CHECK(length(collection_key) BETWEEN 1 AND 64),
10
+ state_observed_at TEXT NOT NULL,
11
+ materialized_source_sha256 TEXT NOT NULL CHECK(
12
+ length(materialized_source_sha256) = 64
13
+ AND materialized_source_sha256 NOT GLOB '*[^0-9a-f]*'
14
+ ),
15
+ PRIMARY KEY(provider, account_key, collection_key),
16
+ FOREIGN KEY(provider, account_key)
17
+ REFERENCES provider_accounts(provider, account_key) ON DELETE RESTRICT
18
+ ) STRICT;
19
+
20
+ -- Legacy mutable state cannot be reconstructed from the newest immutable run:
21
+ -- an exact cached A -> B -> A replay intentionally reuses A's older run. Leave
22
+ -- upgraded realms unbound until one strictly newer live collection observation
23
+ -- establishes the exact current projection.
@@ -0,0 +1,51 @@
1
+ PRAGMA foreign_keys = ON;
2
+
3
+ CREATE TABLE source_execution_receipts (
4
+ id INTEGER PRIMARY KEY,
5
+ provider TEXT NOT NULL CHECK(length(provider) BETWEEN 1 AND 64),
6
+ account_key TEXT NOT NULL COLLATE NOCASE CHECK(length(account_key) BETWEEN 1 AND 256),
7
+ capability TEXT NOT NULL CHECK(length(capability) BETWEEN 1 AND 64),
8
+ operation TEXT NOT NULL CHECK(length(operation) BETWEEN 1 AND 128),
9
+ transport TEXT NOT NULL CHECK(length(transport) BETWEEN 1 AND 64),
10
+ implementation_id TEXT NOT NULL CHECK(length(implementation_id) BETWEEN 1 AND 128),
11
+ implementation_version TEXT CHECK(implementation_version IS NULL OR length(implementation_version) BETWEEN 1 AND 64),
12
+ implementation_sha256 TEXT CHECK(
13
+ implementation_sha256 IS NULL OR (
14
+ length(implementation_sha256) = 64
15
+ AND implementation_sha256 NOT GLOB '*[^0-9a-f]*'
16
+ )
17
+ ),
18
+ contract_sha256 TEXT CHECK(
19
+ contract_sha256 IS NULL OR (
20
+ length(contract_sha256) = 64
21
+ AND contract_sha256 NOT GLOB '*[^0-9a-f]*'
22
+ )
23
+ ),
24
+ external_run_id_sha256 TEXT NOT NULL CHECK(
25
+ length(external_run_id_sha256) = 64
26
+ AND external_run_id_sha256 NOT GLOB '*[^0-9a-f]*'
27
+ ),
28
+ input_sha256 TEXT NOT NULL CHECK(
29
+ length(input_sha256) = 64
30
+ AND input_sha256 NOT GLOB '*[^0-9a-f]*'
31
+ ),
32
+ outcome TEXT NOT NULL CHECK(outcome IN ('succeeded','failed','indeterminate')),
33
+ usage_json TEXT NOT NULL CHECK(json_valid(usage_json)),
34
+ cost_json TEXT NOT NULL CHECK(json_valid(cost_json)),
35
+ metadata_json TEXT NOT NULL CHECK(json_valid(metadata_json)),
36
+ started_at TEXT NOT NULL,
37
+ completed_at TEXT,
38
+ created_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP,
39
+ UNIQUE(provider, account_key, operation, external_run_id_sha256)
40
+ ) STRICT;
41
+
42
+ CREATE INDEX source_execution_receipts_account
43
+ ON source_execution_receipts(provider, account_key, capability, created_at, id);
44
+
45
+ CREATE TRIGGER source_execution_receipts_immutable_update
46
+ BEFORE UPDATE ON source_execution_receipts FOR EACH ROW
47
+ BEGIN SELECT RAISE(ABORT, 'source execution receipts are immutable'); END;
48
+
49
+ CREATE TRIGGER source_execution_receipts_immutable_delete
50
+ BEFORE DELETE ON source_execution_receipts FOR EACH ROW
51
+ BEGIN SELECT RAISE(ABORT, 'source execution receipts are immutable'); END;
@@ -0,0 +1,125 @@
1
+ PRAGMA foreign_keys = ON;
2
+
3
+ -- Live provider accounts converge on the same authorization-binding and
4
+ -- account-realm graph used by Beeper. The legacy provider_accounts table is
5
+ -- retained as immutable compatibility evidence for one release.
6
+ DROP TRIGGER provider_checkpoint_run_update;
7
+ CREATE TRIGGER provider_checkpoint_run_update
8
+ BEFORE UPDATE OF provider,account_key,stream_key,last_run_id ON provider_checkpoints
9
+ FOR EACH ROW
10
+ BEGIN
11
+ SELECT CASE WHEN NEW.provider <> OLD.provider OR NEW.account_key <> OLD.account_key OR NEW.stream_key <> OLD.stream_key
12
+ THEN RAISE(ABORT, 'checkpoint identity is immutable') END;
13
+ SELECT CASE WHEN NEW.last_run_id <= OLD.last_run_id
14
+ THEN RAISE(ABORT, 'checkpoint run must advance') END;
15
+ SELECT CASE WHEN NOT EXISTS (
16
+ SELECT 1 FROM source_runs run
17
+ WHERE run.id = NEW.last_run_id AND run.provider = NEW.provider AND run.account_key = NEW.account_key
18
+ ) THEN RAISE(ABORT, 'checkpoint run realm mismatch') END;
19
+ END;
20
+
21
+ ALTER TABLE provider_checkpoints
22
+ ADD COLUMN source_realm_id INTEGER REFERENCES source_realms(id) ON DELETE RESTRICT;
23
+ CREATE INDEX provider_checkpoints_realm
24
+ ON provider_checkpoints(source_realm_id, stream_key);
25
+
26
+ ALTER TABLE provider_collection_states
27
+ ADD COLUMN source_realm_id INTEGER REFERENCES source_realms(id) ON DELETE RESTRICT;
28
+ CREATE INDEX provider_collection_states_realm
29
+ ON provider_collection_states(source_realm_id, collection_key);
30
+
31
+ CREATE TABLE source_account_convergence (
32
+ provider TEXT NOT NULL CHECK(length(provider) BETWEEN 1 AND 64),
33
+ account_key TEXT NOT NULL COLLATE NOCASE CHECK(length(account_key) BETWEEN 1 AND 256),
34
+ source_realm_id INTEGER NOT NULL REFERENCES source_realms(id) ON DELETE RESTRICT,
35
+ migrated_at TEXT NOT NULL,
36
+ evidence_sha256 TEXT NOT NULL CHECK(
37
+ length(evidence_sha256) = 64
38
+ AND evidence_sha256 NOT GLOB '*[^0-9a-f]*'
39
+ ),
40
+ PRIMARY KEY(provider, account_key),
41
+ UNIQUE(source_realm_id)
42
+ ) STRICT;
43
+
44
+ CREATE TRIGGER provider_resources_realm_insert
45
+ BEFORE INSERT ON provider_resources
46
+ WHEN NEW.source_realm_id IS NOT NULL
47
+ BEGIN
48
+ SELECT CASE WHEN NOT EXISTS (
49
+ SELECT 1 FROM source_realms realm
50
+ WHERE realm.id=NEW.source_realm_id
51
+ AND realm.authority=NEW.provider
52
+ AND realm.account_key=NEW.account_key
53
+ ) THEN RAISE(ABORT, 'provider resource realm mismatch') END;
54
+ END;
55
+
56
+ CREATE TRIGGER provider_resources_realm_update
57
+ BEFORE UPDATE OF source_realm_id,provider,account_key ON provider_resources
58
+ BEGIN
59
+ SELECT CASE WHEN OLD.source_realm_id IS NOT NULL AND NEW.source_realm_id IS NULL
60
+ THEN RAISE(ABORT, 'provider resource realm cannot be detached') END;
61
+ SELECT CASE WHEN NEW.source_realm_id IS NOT NULL AND NOT EXISTS (
62
+ SELECT 1 FROM source_realms realm
63
+ WHERE realm.id=NEW.source_realm_id
64
+ AND realm.authority=NEW.provider
65
+ AND realm.account_key=NEW.account_key
66
+ ) THEN RAISE(ABORT, 'provider resource realm mismatch') END;
67
+ END;
68
+
69
+ CREATE TRIGGER provider_checkpoints_realm_insert
70
+ BEFORE INSERT ON provider_checkpoints
71
+ WHEN NEW.source_realm_id IS NOT NULL
72
+ BEGIN
73
+ SELECT CASE WHEN NOT EXISTS (
74
+ SELECT 1 FROM source_realms realm
75
+ WHERE realm.id=NEW.source_realm_id
76
+ AND realm.authority=NEW.provider
77
+ AND realm.account_key=NEW.account_key
78
+ ) THEN RAISE(ABORT, 'provider checkpoint realm mismatch') END;
79
+ END;
80
+
81
+ CREATE TRIGGER provider_checkpoints_realm_update
82
+ BEFORE UPDATE OF source_realm_id,provider,account_key ON provider_checkpoints
83
+ BEGIN
84
+ SELECT CASE WHEN OLD.source_realm_id IS NOT NULL AND NEW.source_realm_id IS NULL
85
+ THEN RAISE(ABORT, 'provider checkpoint realm cannot be detached') END;
86
+ SELECT CASE WHEN NEW.source_realm_id IS NOT NULL AND NOT EXISTS (
87
+ SELECT 1 FROM source_realms realm
88
+ WHERE realm.id=NEW.source_realm_id
89
+ AND realm.authority=NEW.provider
90
+ AND realm.account_key=NEW.account_key
91
+ ) THEN RAISE(ABORT, 'provider checkpoint realm mismatch') END;
92
+ END;
93
+
94
+ CREATE TRIGGER provider_collection_states_realm_insert
95
+ BEFORE INSERT ON provider_collection_states
96
+ WHEN NEW.source_realm_id IS NOT NULL
97
+ BEGIN
98
+ SELECT CASE WHEN NOT EXISTS (
99
+ SELECT 1 FROM source_realms realm
100
+ WHERE realm.id=NEW.source_realm_id
101
+ AND realm.authority=NEW.provider
102
+ AND realm.account_key=NEW.account_key
103
+ ) THEN RAISE(ABORT, 'provider collection realm mismatch') END;
104
+ END;
105
+
106
+ CREATE TRIGGER provider_collection_states_realm_update
107
+ BEFORE UPDATE OF source_realm_id,provider,account_key ON provider_collection_states
108
+ BEGIN
109
+ SELECT CASE WHEN OLD.source_realm_id IS NOT NULL AND NEW.source_realm_id IS NULL
110
+ THEN RAISE(ABORT, 'provider collection realm cannot be detached') END;
111
+ SELECT CASE WHEN NEW.source_realm_id IS NOT NULL AND NOT EXISTS (
112
+ SELECT 1 FROM source_realms realm
113
+ WHERE realm.id=NEW.source_realm_id
114
+ AND realm.authority=NEW.provider
115
+ AND realm.account_key=NEW.account_key
116
+ ) THEN RAISE(ABORT, 'provider collection realm mismatch') END;
117
+ END;
118
+
119
+ CREATE TRIGGER source_account_convergence_immutable_update
120
+ BEFORE UPDATE ON source_account_convergence FOR EACH ROW
121
+ BEGIN SELECT RAISE(ABORT, 'source account convergence evidence is immutable'); END;
122
+
123
+ CREATE TRIGGER source_account_convergence_immutable_delete
124
+ BEFORE DELETE ON source_account_convergence FOR EACH ROW
125
+ BEGIN SELECT RAISE(ABORT, 'source account convergence evidence is immutable'); END;