@aiaiai-pt/frankctl 0.7.2 → 0.8.1

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,109 @@
1
+ # Stage 11: all 237,808 regional schedule rows projected to the existing B3
2
+ # stop_time type. The source _record_id is generated once from the declared
3
+ # composite stream key (trip_id, Ordem, IdParagem), persisted in Bronze, and
4
+ # passed through unchanged here. Point-in-time profile: 237,808 non-null and
5
+ # distinct _record_id values, 8,964 trip keys, 2,411 stop keys, and zero
6
+ # relationship misses. These counts are evidence, never row limits.
7
+
8
+ apiVersion: frank.platform/v1
9
+ kind: IdentityPolicy
10
+ metadata:
11
+ name: cira-stop-time-external-id
12
+ spec:
13
+ name: cira-stop-time-external-id
14
+ strategy: passthrough
15
+ source_fields:
16
+ - _record_id
17
+ normalizers: []
18
+ emit_format: "cira:bdtransportes:stop_time:{resolved_key}"
19
+ collision_policy: error
20
+ ---
21
+ apiVersion: frank.platform/v1
22
+ kind: Pipeline
23
+ metadata:
24
+ name: cira_transport_stop_times
25
+ spec:
26
+ name: cira_transport_stop_times
27
+ description: Complete CIRA regional stop times with stable source identity and existing trip/stop links.
28
+ source_ids:
29
+ - cira-bdtransportes-horarios
30
+ schedule_config:
31
+ type: manual
32
+ steps:
33
+ - name: stop_time_rows
34
+ kind: field_mapping
35
+ params:
36
+ field_mappings:
37
+ - target_column: external_id
38
+ mapping_kind: identity
39
+ identity_policy:
40
+ name: cira-stop-time-external-id
41
+ field_order: 0
42
+ - source_column: schedule
43
+ target_column: arrival_time
44
+ mapping_kind: source_expression
45
+ field_order: 1
46
+ - source_column: schedule
47
+ target_column: departure_time
48
+ mapping_kind: source_expression
49
+ field_order: 2
50
+ - source_column: ordem
51
+ target_column: stop_sequence
52
+ mapping_kind: source_expression
53
+ custom_sql: "try_cast(${source} AS integer)"
54
+ field_order: 3
55
+ - source_column: trip_id
56
+ target_column: trip_id
57
+ mapping_kind: source_expression
58
+ field_order: 4
59
+ - source_column: id_paragem
60
+ target_column: stop_key
61
+ mapping_kind: source_expression
62
+ custom_sql: "concat('stop:',cast(try_cast(${source} AS bigint) AS varchar))"
63
+ field_order: 5
64
+ depends_on: []
65
+ emits_to: backing_dataset
66
+ config:
67
+ output_layer: silver
68
+ ---
69
+ apiVersion: frank.platform/v1
70
+ kind: BackingDataset
71
+ metadata:
72
+ name: cira_transport_stop_times
73
+ dependsOn:
74
+ - entityTypeId: transit_trip
75
+ ontologyTenantId: cira
76
+ - entityTypeId: transit_stop
77
+ ontologyTenantId: cira
78
+ spec:
79
+ entity_type_id: stop_time
80
+ entity_type_name: Stop Time
81
+ ontology_tenant_id: cira
82
+ pipeline: cira_transport_stop_times
83
+ sync_mode: manual
84
+ primary_key_column: external_id
85
+ title_key_column: external_id
86
+ property_mappings:
87
+ - column: external_id
88
+ property: external_id
89
+ type: varchar
90
+ is_primary_key: true
91
+ - column: arrival_time
92
+ property: arrival_time
93
+ type: varchar
94
+ - column: departure_time
95
+ property: departure_time
96
+ type: varchar
97
+ - column: stop_sequence
98
+ property: stop_sequence
99
+ type: integer
100
+ - column: trip_id
101
+ property: trip
102
+ is_relationship: true
103
+ target_type: transit_trip
104
+ target_key: service_id
105
+ - column: stop_key
106
+ property: stop
107
+ is_relationship: true
108
+ target_type: transit_stop
109
+ target_key: code
@@ -98,6 +98,16 @@ spec:
98
98
  - 580
99
99
  - 591
100
100
  - 592
101
+ regional-transport-stop-times:
102
+ title: Regional transport stop times with declarative identity
103
+ status: declared_pending
104
+ manifests:
105
+ - 11_stop_time.yaml
106
+ required_sources:
107
+ - cira-bdtransportes-horarios
108
+ issues:
109
+ - 592
110
+ - 650
101
111
  regional-transport-avl:
102
112
  title: Regional transport current vehicle positions
103
113
  status: live_proven
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aiaiai-pt/frankctl",
3
- "version": "0.7.2",
3
+ "version": "0.8.1",
4
4
  "description": "Terminal-first client for the Frank Low-Code Pipeline platform",
5
5
  "homepage": "https://franksdocs.nomadriver.co",
6
6
  "repository": {
@@ -85,20 +85,22 @@ an explicit subset. Repeat `--set frank_tenant_prefix=<8-hex>` and
85
85
  ontology tenant. The CIRA pack treats ontology schema as externally owned and therefore
86
86
  contains no `ensure_schema` mutation.
87
87
 
88
- A pipeline is one multi-document YAML file describing three kinds of object under the
88
+ A pipeline is one multi-document YAML file describing four kinds of object under the
89
89
  `frank.platform/v1` API version:
90
90
 
91
91
  | `kind` | Role |
92
92
  |--------|------|
93
93
  | `Source` | Extract/Load config — connector `pattern_id`, streams, schedule → Iceberg **bronze** |
94
+ | `IdentityPolicy` | Stable-key recipe reconciled and pinned before a consuming transform |
94
95
  | `Pipeline` | Transform steps (SQL/Python) turning bronze → **silver/gold** |
95
96
  | `BackingDataset` | Maps a silver table's columns to **ontology** entity properties |
96
97
 
97
98
  Objects reference each other by `metadata.name`, so one file can be self-contained and
98
- version-controllable. Commit only reviewed, secret-free declarations. Structurally
99
- declared Source credentials are stored in Vault and written with `sources credentials
100
- set|rotate --values-file -`; `source_config` and export contain only ordinary values plus
101
- the opaque tenant-owned `credential_ref`.
99
+ version-controllable. Credential behavior follows explicit `SOURCE_CREDENTIAL_MODE`:
100
+ current base Compose uses `legacy_inline`, where write-only values persist in redacted
101
+ `source_config` and `sources credentials set|rotate` is unavailable; immutable release
102
+ `vault` mode uses those commands and keeps only the opaque `credential_ref` in exports.
103
+ Commit only reviewed, secret-free declarations in either mode.
102
104
 
103
105
  ### The loop: scaffold → edit → apply
104
106
 
@@ -139,8 +141,8 @@ separate target entity readback.
139
141
 
140
142
  | Flag | What it does |
141
143
  |------|--------------|
142
- | `--dry-run` | Client-side parse + cross-ref resolve. Catches malformed YAML and broken references. No server call. |
143
- | `--dry-run-server` | Server-side preflight against the catalog + DB without persisting. Catches unknown `pattern_id`s, would-be immutable-diff conflicts, and unresolved refs. |
144
+ | `--dry-run` | Client-side parse + local cross-ref validation. Catches malformed YAML and broken same-file references. No server call. |
145
+ | `--dry-run-server` | Server-side preflight against the catalog + DB without persisting. Catches unknown `pattern_id`s, identity-policy create/noop/patch/new-version state, would-be immutable-diff conflicts, and unresolved refs. |
144
146
  | `--dry-run-server --live-ontology` | Adds an opt-in, GET-only check of ontology tenant, type, properties, relationships, keys, and declared carrier types. It never changes ontology schema or entities. |
145
147
 
146
148
  Run `--dry-run-server` in CI on every change; run `apply --wait` to promote.
@@ -157,6 +159,60 @@ BackingDataset and set `spec.sync_mode: manual`. `--wait` topologically orders s
157
159
  dependencies and requires external dependencies in the same ontology tenant to be
158
160
  already `synced`. Cycles and self-referential relationships fail before execution.
159
161
 
162
+ ### Stable identity in declarative pipelines
163
+
164
+ Declare a tenant-owned recipe once, then reference it by name from an identity field
165
+ mapping. `frankctl` applies in `Source → IdentityPolicy → Pipeline → BackingDataset`
166
+ order and replaces the symbolic reference with the exact returned policy ID/version;
167
+ UUIDs do not belong in tracked YAML.
168
+
169
+ ```yaml
170
+ apiVersion: frank.platform/v1
171
+ kind: IdentityPolicy
172
+ metadata: { name: source-record-id }
173
+ spec:
174
+ name: source-record-id
175
+ strategy: passthrough
176
+ source_fields: [_record_id]
177
+ normalizers: []
178
+ emit_format: "my-provider:my-type:{resolved_key}"
179
+ collision_policy: error
180
+ ---
181
+ apiVersion: frank.platform/v1
182
+ kind: Pipeline
183
+ metadata: { name: my-pipeline }
184
+ spec:
185
+ source_ids: [my-source]
186
+ steps:
187
+ - name: project
188
+ kind: field_mapping
189
+ params:
190
+ field_mappings:
191
+ - target_column: external_id
192
+ mapping_kind: identity
193
+ identity_policy: { name: source-record-id }
194
+ field_order: 0
195
+ ```
196
+
197
+ Same-file references contain only `name`; external policies require exact `{name, tier,
198
+ version}`. Never author raw UUID/version/hash pins: `frankctl` resolves symbolic YAML
199
+ and carries the server-returned recipe hash as an internal optimistic precondition.
200
+
201
+ Policy reconciliation reports `create`, `noop`, `patch`, or `new-version`. First use freezes
202
+ a recipe globally; later changes version it. Reads compute tenant-live `used_in`, including
203
+ for system policies, without overwriting a shared system cache. Multi-document apply is
204
+ sequential, not atomic: correct a failure and reapply the complete declaration.
205
+
206
+ Pipeline activation locks Pipeline, affected Transforms in deterministic UUID order with
207
+ refreshed mapping/source snapshots, then old+new policies in UUID/version order. Direct
208
+ Transform PUT/DELETE locks Transform, then sorted policies. This prevents deadlock and
209
+ stale `used_in`. A changed recipe still returns `409 identity_policy_recipe_changed`;
210
+ reapply the complete file. Dry-run observes state but does not reserve it.
211
+
212
+ Migration `s43_identity_policy_frozen` scans only historical `mapping_kind: identity` pins.
213
+ It recognizes canonical/uppercase/hyphenless/braced/URN UUIDs and normalized positive
214
+ versions without casting malformed JSON. Author symbolically; never mutate ontology schema.
215
+
160
216
  ### Immutable state & when to force
161
217
 
162
218
  Backing Datasets carry operational state (ontology cursor, sync history). If a change
@@ -179,8 +235,10 @@ to a file for review:
179
235
  frankctl pipelines export <pipeline-id> > pipeline.yaml
180
236
  ```
181
237
 
182
- The export includes ordinary Source configuration, opaque credential references, and
183
- config-only stream declarations, but omits credential values and stream runtime state.
238
+ The export includes ordinary Source configuration, opaque credential references,
239
+ config-only stream declarations, complete Pipeline steps, and portable symbolic
240
+ IdentityPolicy references. It omits credential values, stream runtime state, and
241
+ tenant-local policy UUIDs.
184
242
 
185
243
  ### Minimal manifest shape
186
244
 
@@ -429,6 +487,9 @@ you can review, edit, and feed straight into `pipelines apply`.
429
487
  - **401 after login**: your token expired and refresh failed — `frankctl auth login` again.
430
488
  - **`409 immutable_diff` on apply**: a Backing Dataset change would destroy state; see
431
489
  `--allow-recreate` above and decide deliberately.
490
+ - **`409 identity_policy_recipe_changed` on apply**: replay the complete manifest so
491
+ policy reconciliation returns a fresh pin; do not add runtime pins to YAML or retry
492
+ only the Pipeline request.
432
493
  - **`apply` did nothing**: that's idempotency — the declared state already matches. Use
433
494
  `-v` to see the reconcile plan, or `--dry-run-server` to preview.
434
495