Skip to content

feat(lawful-basis): emit lawful basis and external media on every platform - #12

Merged
howethomas merged 6 commits into
mainfrom
thomashowe/con-1083-lawful-basis-all-platforms
Sep 25, 2026
Merged

howethomas merged 6 commits into
mainfrom
thomashowe/con-1083-lawful-basis-all-platforms

Conversation

@howethomas

@howethomas howethomas commented Sep 25, 2026 •

Copy link
Copy Markdown
Contributor

Stacked on #11 (which is stacked on #10). Base is that branch, so this diff shows only this change. Merge in order.

The shared core already supported a lawful basis attachment and external media, but only Telnyx turned them on. Asterisk, Bandwidth, FreeSWITCH and Twilio emitted vCons with no lawful basis and always inlined or referenced media.

Changes

  • All four remaining webhook factories now build the media publisher and lawful basis from config and pass them to their builder, the same block Telnyx already used. No signature-validation code touched.
  • Lawful basis attachment shape, targeting draft-ietf-vcon-vcon-core-04. vcon-lib 0.9.6's add_lawful_basis_attachment emits body as an object and omits party/dialog/mediatype. core/lawful_basis.py sets party: 0, dialog: 0, and mediatype: "application/json", and leaves body as the object vcon-lib already produces: under -04 §2.3.2 (CDDL body: any), body for encoding: "json" is the raw JSON value, not a json.dumps string. (An earlier revision of this PR stringified it against a stale schema fork that typed body as a string; that was wrong for -04 and has been reverted.) Result: purpose: "lawful_basis", encoding: "json", object body, "lawful_basis" in extensions.
  • Unset LAWFUL_BASIS behaves as before: a warning and no attachment. No default basis in code.
  • Every attachment now carries start, party, dialog, and mediatype, which draft-ietf-vcon-vcon-core-04's Attachment Object requires (mediatype whenever body is present). Missing values are backfilled in core/base_builder.py with the vCon's created_at, index 0, and application/json respectively; existing values are never overwritten.
  • README common-configuration table lists LAWFUL_BASIS* and MEDIA_* as applying to every platform, and documents that JSON-encoded attachment bodies are raw values under -04, not json.dumps strings.

Tests

15 new tests: per platform, lawful basis configured (exact attachment shape, including mediatype), lawful basis unset (no attachment, warning logged), and MEDIA_BACKEND=filesystem (dialog has url + content_hash, no inline body); plus required attachment fields (start/party/dialog/mediatype on every attachment). Full stack: 539 passed.

Schema validation

One sample vCon per platform, built with LAWFUL_BASIS=consent and MEDIA_BACKEND=filesystem from test fixtures, validated with jsonschema against the working group's current schema (vendored at vcon-adapter-template's tests/schema/vcon_json_schema.json, WG main @ fdcf2f5, matching the -04 appendix):

asterisk   VALID
bandwidth  VALID
freeswitch VALID
twilio     VALID
telnyx     VALID

All five pass. The start/party/dialog and mediatype gaps are closed, and retargeting to -04 (raw JSON body, not a string) also resolves what looked like a second defect in the tags attachment: its body is a JSON array from vcon-lib's add_tag, which is a valid body value under -04's body: any — it only failed against the stale schema fork's body: string constraint.

One remaining, separate defect: MEDIA_BACKEND=embed (the default) still fails schema validation — dialog[0].encoding is "base64", which is not in the schema's ["base64url", "json", "none"] enum. That's in the inline-audio path in core/base_builder.py, unrelated to lawful basis or attachments, tracked separately as CON-1100.

Refs CON-1083

🤖 Generated with Claude Code

@howethomas
howethomas added this pull request to stack #13 September 25, 2026 21:49
@howethomas
howethomas force-pushed the thomashowe/con-1083-lawful-basis-all-platforms branch from 7bb6768 to e6d16b7 Compare September 25, 2026 21:49
Base automatically changed from thomashowe/con-1084-packaging-container to main September 25, 2026 21:53
howethomas and others added 6 commits September 25, 2026 17:53
… to 0

vcon-lib 0.9.6's add_lawful_basis_attachment() emits `body` as the
attachment dict itself, not the JSON string `encoding: "json"` implies
and the vCon schema requires (`body` is `type: string`). Stringify the
attachment vcon-lib just appended rather than hand-rolling the shape.

Also default party_index/dialog_index to 0 instead of leaving them
unset: every adapter builder here produces exactly one dialog and
treats party 0 as the recording's subject, and the vCon schema
requires both fields on every attachment.

Updates the existing lawful-basis tests to decode the body before
asserting on its contents.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…hooks

Asterisk, Bandwidth, FreeSWITCH and Twilio built their vCon builders
without lawful_basis or publisher, so only Telnyx ever emitted a
lawful_basis attachment or re-hosted media instead of embedding it
inline. The per-platform config classes already had
build_lawful_basis()/build_publisher() via BaseConfig, and the builders
already accepted both kwargs; only the webhook factories were missing
the wiring, so this follows the same pattern already used in
adapters/telnyx/webhook.py.

Unset LAWFUL_BASIS keeps prior behaviour: no attachment, a startup
warning. MEDIA_BACKEND=embed (the default) keeps prior behaviour too.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Asterisk, Bandwidth, FreeSWITCH and Twilio each get the same coverage
Telnyx already had: a configured LAWFUL_BASIS produces a correctly
shaped attachment (string body, purpose/party/dialog/encoding,
extensions), an unset one produces neither an attachment nor a
warning-free log, and MEDIA_BACKEND=filesystem produces a dialog with
url + content_hash and no inline body.

Twilio's file lives at the tests/ top level rather than under
tests/adapters/, matching how its other tests are already organized in
this repo.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Both apply to every adapter now, not just Telnyx.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ment

The official schema (draft-ietf-vcon-vcon-core, Attachment Object) lists
start, party, and dialog as required on every attachment, not optional.
vcon-lib's own add_tag() already sets party/dialog on the tags
attachment it creates but never start, and its own validator does not
check for any of the three, so every vCon this monorepo produced was
schema-invalid on that attachment regardless of lawful basis.

BaseVconBuilder.build() now backfills all three on every attachment it
emits, once, after tags and lawful_basis are both added: start from the
vCon's own created_at (the attachment is produced at conversion time,
which is what "sent/exchanged" means here, in the same ISO 8601 +
timezone format created_at already uses), party/dialog defaulting to 0.
Uses setdefault, so an attachment that already carries a value (the
lawful_basis attachment already sets its own party/dialog) is never
overwritten.

Regenerating sample vCons per platform against the official schema
(vcon_json_schema.json) confirms the start/party/dialog gap is closed,
but surfaces two further, distinct, pre-existing schema mismatches this
fix does not touch:

- The embedded-audio path sets dialog.encoding = "base64", which is not
  in the schema's enum (base64url | json | none).
- The tags attachment's body is a JSON array (vcon-lib's own add_tag()
  shape), not the string the schema requires for `body`. Stringifying
  it here would silently break vcon-lib's own Vcon.get_tag() (which
  iterates body expecting list items) and any later Vcon.add_tag() call
  on the same vCon (which appends to body expecting a list), so it is
  left alone rather than worked around.

Both are unrelated to lawful basis and out of scope for this card.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…core-04

Reverts the earlier json.dumps stringification of the lawful_basis
attachment body: -04 §2.3.2 (CDDL body: any) makes body the raw JSON
value for encoding: "json", not a string. That fix was written against
a stale schema fork that typed body as a string; the working group's
current schema (vendored at vcon-adapter-template's tests/schema) does
not. vcon-lib 0.9.6's object body was already correct and is left
untouched.

mediatype is required whenever body is present (-04's Attachment
Object). vcon-lib sets it on neither the lawful_basis attachment nor
the tags attachment from add_tag(), so LawfulBasisConfig.apply() and
the attachment backfill loop in BaseVconBuilder.build() both now
setdefault mediatype to "application/json" (every attachment either
of them touches is encoding: "json").

Updates every test that asserted body was a JSON string to assert it
is the JSON object/array instead, and adds mediatype coverage
alongside the existing start/party/dialog checks.

README: documents that JSON-encoded attachment bodies are raw values
under -04, not json.dumps strings.

Regenerated sample vCons (asterisk, bandwidth, freeswitch, twilio,
telnyx) with LAWFUL_BASIS set and MEDIA_BACKEND=filesystem, validated
against the vendored working-group schema (main @ fdcf2f5): all five
VALID. (The embedded-audio dialog.encoding="base64" mismatch is
unrelated and tracked separately as CON-1100.)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@howethomas
howethomas force-pushed the thomashowe/con-1083-lawful-basis-all-platforms branch from e6d16b7 to 10e6621 Compare September 25, 2026 21:53
@howethomas
howethomas merged commit 14dac2e into main Sep 25, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant