Files
member-console/internal/integration/observed.sql.go
T
cgalo5758 23d5bad024 Add integration delivery core: provider connections, desired-state
records, outbox, lifecycle requests, relay, push, read-back, and FedWiki
lifecycle workflows

- Add core.provider_connections table and unique provider connection per
  provider
- Add core.target_mappings table linking connections to resource keys
  and targets
- Add core.desired_state_records table storing desired entitlements per
  connection and recipient with generation and content hash
- Add core.observed_projections table for application-reported state per
  connection and recipient
- Add core.lifecycle_requests table recording lifecycle requests by
  instance and generation
- Add core.person_cut_offs table and setter function to record persons
  cut off everywhere
- Add core.desired_state_outbox table with triggers on entitlement,
  membership, organization, connection, mapping, and cut-off tables to
  write change notices
- Enforce constraints on recipients, generations, and statuses across
  tables
- Implement desired_state_content SQL function computing record content
  per recipient rules
- Implement relay workflow to claim outbox rows, expand to records,
  recompute records, and dispatch pushes
- Implement push workflow to push records, handle transport answers, and
  record push results
- Implement read-back workflow to page through application state, update
  projections, and raise records as needed
- Add lifecycle request writers with idempotent recording, finishing,
  and acknowledgment
- Add FedWiki integration lifecycle workflows for create, set status,
  swap, delete, and reconcile
- Add FedWiki web handlers and forms for lifecycle requests, including
  create form with instance and generation
- Add tests for schema, triggers, relay, push, read-back, lifecycle
  requests, FedWiki workflows, and operator pages
- Add documentation updates for provider integration, entitlements,
  identity, organizations, lifecycle, and FedWiki integration
- Add benchmarks for delivery and push workflows
- Ensure schema-per-provider for in-tree integrations; connector state
  lives in generic core tables keyed by connection
- Secure functions and triggers with SECURITY DEFINER and restrict
  core_writer role privileges accordingly
- Handle Temporal client integration for workflow execution and
  scheduling
- Provide a recording transport for integration tests
- Close Issues 32 and 33 by implementing provider connections and
  desired-state records with lifecycle requests and relay mechanisms
2026-10-06 03:46:15 -05:00

302 lines
12 KiB
Go

// SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Commercial
// SPDX-FileCopyrightText: 2025-2026 Christian Galo
// Code generated by sqlc. DO NOT EDIT.
// versions:
// sqlc v1.29.0
// source: observed.sql
package integration
import (
"context"
"database/sql"
"encoding/json"
"time"
"github.com/google/uuid"
)
const getObservedProjection = `-- name: GetObservedProjection :one
SELECT projection_id, connection_id, person_id, org_id, applied_generation, applied_content_hash, observed, result, result_generation, next_action, observed_at, ahead_generation, ahead_at, created_at, updated_at FROM core.observed_projections op
WHERE (op.connection_id = $1::uuid AND op.person_id = $2::uuid)
OR (op.connection_id = $1::uuid AND op.org_id = $3::uuid)
`
type GetObservedProjectionParams struct {
ConnectionID string `json:"connection_id"`
PersonID uuid.NullUUID `json:"person_id"`
OrgID uuid.NullUUID `json:"org_id"`
}
// The projection at one address; exactly one of person_id and org_id is set.
// sql.ErrNoRows when the application has never answered for the recipient.
func (q *Queries) GetObservedProjection(ctx context.Context, arg GetObservedProjectionParams) (ObservedProjection, error) {
row := q.db.QueryRowContext(ctx, getObservedProjection, arg.ConnectionID, arg.PersonID, arg.OrgID)
var i ObservedProjection
err := row.Scan(
&i.ProjectionID,
&i.ConnectionID,
&i.PersonID,
&i.OrgID,
&i.AppliedGeneration,
&i.AppliedContentHash,
&i.Observed,
&i.Result,
&i.ResultGeneration,
&i.NextAction,
&i.ObservedAt,
&i.AheadGeneration,
&i.AheadAt,
&i.CreatedAt,
&i.UpdatedAt,
)
return i, err
}
const markObservedProjectionAheadForOrganization = `-- name: MarkObservedProjectionAheadForOrganization :exec
INSERT INTO core.observed_projections
(connection_id, org_id, observed_at, ahead_generation, ahead_at)
VALUES ($1::uuid, $2::uuid, NOW(),
$3::bigint, NOW())
ON CONFLICT (connection_id, org_id) DO UPDATE SET
ahead_generation = excluded.ahead_generation,
ahead_at = excluded.ahead_at
`
type MarkObservedProjectionAheadForOrganizationParams struct {
ConnectionID string `json:"connection_id"`
OrgID string `json:"org_id"`
AheadGeneration int64 `json:"ahead_generation"`
}
// The restore marker for an organization; as the person form.
func (q *Queries) MarkObservedProjectionAheadForOrganization(ctx context.Context, arg MarkObservedProjectionAheadForOrganizationParams) error {
_, err := q.db.ExecContext(ctx, markObservedProjectionAheadForOrganization, arg.ConnectionID, arg.OrgID, arg.AheadGeneration)
return err
}
const markObservedProjectionAheadForPerson = `-- name: MarkObservedProjectionAheadForPerson :exec
INSERT INTO core.observed_projections
(connection_id, person_id, observed_at, ahead_generation, ahead_at)
VALUES ($1::uuid, $2::uuid, NOW(),
$3::bigint, NOW())
ON CONFLICT (connection_id, person_id) DO UPDATE SET
ahead_generation = excluded.ahead_generation,
ahead_at = excluded.ahead_at
`
type MarkObservedProjectionAheadForPersonParams struct {
ConnectionID string `json:"connection_id"`
PersonID string `json:"person_id"`
AheadGeneration int64 `json:"ahead_generation"`
}
// The restore marker (design D19): the application reported a generation
// above the record's, or the record's own generation with another hash. Only
// the latest occurrence is kept.
func (q *Queries) MarkObservedProjectionAheadForPerson(ctx context.Context, arg MarkObservedProjectionAheadForPersonParams) error {
_, err := q.db.ExecContext(ctx, markObservedProjectionAheadForPerson, arg.ConnectionID, arg.PersonID, arg.AheadGeneration)
return err
}
const recordPushResultForOrganization = `-- name: RecordPushResultForOrganization :exec
INSERT INTO core.observed_projections
(connection_id, org_id, applied_generation, applied_content_hash,
result, result_generation, next_action, observed_at)
VALUES ($1::uuid, $2::uuid,
$3::bigint, $4::bytea,
$5::text, $6::bigint,
$7::text, $8::timestamptz)
ON CONFLICT (connection_id, org_id) DO UPDATE SET
result = excluded.result,
result_generation = excluded.result_generation,
next_action = excluded.next_action,
observed_at = excluded.observed_at,
applied_content_hash = CASE
WHEN excluded.applied_generation IS NOT NULL
AND (observed_projections.applied_generation IS NULL
OR excluded.applied_generation >= observed_projections.applied_generation)
THEN excluded.applied_content_hash
ELSE observed_projections.applied_content_hash END,
applied_generation = CASE
WHEN excluded.applied_generation IS NOT NULL
AND (observed_projections.applied_generation IS NULL
OR excluded.applied_generation >= observed_projections.applied_generation)
THEN excluded.applied_generation
ELSE observed_projections.applied_generation END
`
type RecordPushResultForOrganizationParams struct {
ConnectionID string `json:"connection_id"`
OrgID string `json:"org_id"`
AppliedGeneration sql.NullInt64 `json:"applied_generation"`
AppliedContentHash []byte `json:"applied_content_hash"`
Result string `json:"result"`
ResultGeneration int64 `json:"result_generation"`
NextAction sql.NullString `json:"next_action"`
ObservedAt time.Time `json:"observed_at"`
}
// A push answer for an organization; as the person form.
func (q *Queries) RecordPushResultForOrganization(ctx context.Context, arg RecordPushResultForOrganizationParams) error {
_, err := q.db.ExecContext(ctx, recordPushResultForOrganization,
arg.ConnectionID,
arg.OrgID,
arg.AppliedGeneration,
arg.AppliedContentHash,
arg.Result,
arg.ResultGeneration,
arg.NextAction,
arg.ObservedAt,
)
return err
}
const recordPushResultForPerson = `-- name: RecordPushResultForPerson :exec
INSERT INTO core.observed_projections
(connection_id, person_id, applied_generation, applied_content_hash,
result, result_generation, next_action, observed_at)
VALUES ($1::uuid, $2::uuid,
$3::bigint, $4::bytea,
$5::text, $6::bigint,
$7::text, $8::timestamptz)
ON CONFLICT (connection_id, person_id) DO UPDATE SET
result = excluded.result,
result_generation = excluded.result_generation,
next_action = excluded.next_action,
observed_at = excluded.observed_at,
applied_content_hash = CASE
WHEN excluded.applied_generation IS NOT NULL
AND (observed_projections.applied_generation IS NULL
OR excluded.applied_generation >= observed_projections.applied_generation)
THEN excluded.applied_content_hash
ELSE observed_projections.applied_content_hash END,
applied_generation = CASE
WHEN excluded.applied_generation IS NOT NULL
AND (observed_projections.applied_generation IS NULL
OR excluded.applied_generation >= observed_projections.applied_generation)
THEN excluded.applied_generation
ELSE observed_projections.applied_generation END
`
type RecordPushResultForPersonParams struct {
ConnectionID string `json:"connection_id"`
PersonID string `json:"person_id"`
AppliedGeneration sql.NullInt64 `json:"applied_generation"`
AppliedContentHash []byte `json:"applied_content_hash"`
Result string `json:"result"`
ResultGeneration int64 `json:"result_generation"`
NextAction sql.NullString `json:"next_action"`
ObservedAt time.Time `json:"observed_at"`
}
// A push answer for a person: the result, the generation it was about and
// the next-action key always replace what was stored; the applied generation
// and its hash move together and only upwards. An applied answer reports the
// pushed generation and hash; an unlinked one reports the generation the
// application still holds and no hash.
func (q *Queries) RecordPushResultForPerson(ctx context.Context, arg RecordPushResultForPersonParams) error {
_, err := q.db.ExecContext(ctx, recordPushResultForPerson,
arg.ConnectionID,
arg.PersonID,
arg.AppliedGeneration,
arg.AppliedContentHash,
arg.Result,
arg.ResultGeneration,
arg.NextAction,
arg.ObservedAt,
)
return err
}
const upsertObservedProjectionForOrganization = `-- name: UpsertObservedProjectionForOrganization :execrows
INSERT INTO core.observed_projections
(connection_id, org_id, applied_generation, applied_content_hash, observed, observed_at)
VALUES ($1::uuid, $2::uuid,
$3::bigint, $4::bytea,
$5::jsonb, $6::timestamptz)
ON CONFLICT (connection_id, org_id) DO UPDATE SET
applied_generation = excluded.applied_generation,
applied_content_hash = excluded.applied_content_hash,
observed = excluded.observed,
observed_at = excluded.observed_at
WHERE excluded.applied_generation IS NOT NULL
AND (observed_projections.applied_generation IS NULL
OR excluded.applied_generation >= observed_projections.applied_generation)
`
type UpsertObservedProjectionForOrganizationParams struct {
ConnectionID string `json:"connection_id"`
OrgID string `json:"org_id"`
AppliedGeneration sql.NullInt64 `json:"applied_generation"`
AppliedContentHash []byte `json:"applied_content_hash"`
Observed json.RawMessage `json:"observed"`
ObservedAt time.Time `json:"observed_at"`
}
// A read-back item for an organization; as the person form.
func (q *Queries) UpsertObservedProjectionForOrganization(ctx context.Context, arg UpsertObservedProjectionForOrganizationParams) (int64, error) {
result, err := q.db.ExecContext(ctx, upsertObservedProjectionForOrganization,
arg.ConnectionID,
arg.OrgID,
arg.AppliedGeneration,
arg.AppliedContentHash,
arg.Observed,
arg.ObservedAt,
)
if err != nil {
return 0, err
}
return result.RowsAffected()
}
const upsertObservedProjectionForPerson = `-- name: UpsertObservedProjectionForPerson :execrows
INSERT INTO core.observed_projections
(connection_id, person_id, applied_generation, applied_content_hash, observed, observed_at)
VALUES ($1::uuid, $2::uuid,
$3::bigint, $4::bytea,
$5::jsonb, $6::timestamptz)
ON CONFLICT (connection_id, person_id) DO UPDATE SET
applied_generation = excluded.applied_generation,
applied_content_hash = excluded.applied_content_hash,
observed = excluded.observed,
observed_at = excluded.observed_at
WHERE excluded.applied_generation IS NOT NULL
AND (observed_projections.applied_generation IS NULL
OR excluded.applied_generation >= observed_projections.applied_generation)
`
type UpsertObservedProjectionForPersonParams struct {
ConnectionID string `json:"connection_id"`
PersonID string `json:"person_id"`
AppliedGeneration sql.NullInt64 `json:"applied_generation"`
AppliedContentHash []byte `json:"applied_content_hash"`
Observed json.RawMessage `json:"observed"`
ObservedAt time.Time `json:"observed_at"`
}
// Observed projections (design D10, D19, D21). Every statement here runs in a
// transaction that already holds the address's advisory lock and the record
// row, so the projection row is the last lock taken. Each write comes in a
// person and an organization form because an upsert names one unique
// constraint as its conflict target.
//
// A read-back item for a person. The stored applied generation is never
// lowered: a page fetched before a push was applied can arrive after the push
// answer, and then changes nothing.
func (q *Queries) UpsertObservedProjectionForPerson(ctx context.Context, arg UpsertObservedProjectionForPersonParams) (int64, error) {
result, err := q.db.ExecContext(ctx, upsertObservedProjectionForPerson,
arg.ConnectionID,
arg.PersonID,
arg.AppliedGeneration,
arg.AppliedContentHash,
arg.Observed,
arg.ObservedAt,
)
if err != nil {
return 0, err
}
return result.RowsAffected()
}