# title
Kitsuno Handshake v0.2 — Vacancy Card (L1 public surface)

# description
L1 representation of a vacancy. This is what a seeker agent fetches from a canonical URL (e.g., kitsuno.ai/handshake/v0.2/cards/{slug}.json) to evaluate via policy_match. L1 mirrors standard job-ad information — what any candidate would see on a public posting. Fields default to L1 per protocol defaults; the tier_overrides field allows posters to demote individual fields to L2 or L3.

# properties.card_authority.description
primary = published by an authenticated vacancy_account (P1/P2/P3). mirror = scraped from a third-party source by Kitsuno's mirror generator (read-only, L1 only, no L2/L3 reachable).

# properties.role_family.description
Coarse role classification (engineering, design, product, marketing, ops, sales, support, hr, finance, legal, executive).

# properties.geo_scope.description
Geographic hiring scope, mirroring seeker-card geography.scope. 'global' = role is open anywhere (no country gate; country_codes optional/ignored for gating). 'regions' = restricted to the explicit country_codes list. Backward-compatible: absent = 'regions', so existing cards behave unchanged. Foreign agents that do not understand this field MUST treat it as 'regions'.

# properties.country_codes.description
One or more ISO 3166-1 alpha-2 codes the role accepts. REQUIRED when geo_scope is 'regions' (the default); omitted/ignored when geo_scope is 'global'.

# properties.country_codes_excluded.description
Countries explicitly excluded even under a 'global' scope or a broad regions list (e.g. open to all of Europe except IT, or global except sanctioned jurisdictions). Exclusions always win, mirroring seeker-card geography.countries_excluded.

# properties.description.description
Short L1 description / lede (≤2000 chars recommended).

# properties.description_full.description
Extended description; default L2 unless poster moves to L1.

# properties.industry_label.description
Surfaced even when posting_visibility=confidential to give seeker signal.

# properties.company_name.description
Required when posting_visibility=named. Withheld at L1 when posting_visibility=confidential (carried in L3 release instead).

# properties.posted_by.description
Display name of the poster (company or recruiter agency name). When posting_visibility=confidential and posting via recruiter, this is the recruiter agency: 'via [Agency Name] — confidential [industry]'.

# properties.posting_on_behalf_of.description
P2 only — the client name when recruiter posts for a named client. NULL when confidential.

# properties.handshake_policy.description
L1 policy criteria the seeker agent evaluates via policy_match before firing L1. Logical AND across all listed criteria.

# properties.l2_structured_questions.description
Poster-authored structured screening questions answered by seeker agent at L2 from PRS. Format: array of {id, question_text, answer_type, required}.

# properties.l2_free_form_questions.description
Poster-authored free-form questions answered by seeker agent at L2 from PRS via LLM (e.g., 'Why this role?').

# properties.application_requirements.description
Poster-authored materials the applicant PRODUCES and delivers (a portfolio link, a work sample, a written answer, a GitHub repo, etc.). DISTINCT from l2_structured_questions, which are answered from the seeker's existing PRS at L2. Application requirements are L3 CONTENT: the artifact itself never crosses at L2 — at L2 the vacancy agent sees only a per-requirement provided/not boolean. PROTOCOL RULES (any conforming implementation MUST honour these): (1) a requirement's content is L3 and is never disclosed below L3; (2) at L2 a requirement reduces to a provided/not-provided boolean; (3) importance=required && not provided => the match is NOT rejected — it becomes an INVITATION TO APPLY (the seeker is told 'you match; provide X to apply'), and the act of providing X IS the application and the per-vacancy L3 consent for this conversation; (4) importance=wished/supporting never block. Whether a requirement is 'provided' from a seeker's STANDING card vs. requires an active apply is an IMPLEMENTATION choice of each operator against their own card model — only requirement types that map unambiguously to a standing card field can be pre-satisfied; types whose relevant instance is per-vacancy (which repo, which certificate, which writing sample, free-text answers) are satisfied only by applying. Interacts with l3_intake_mode: even under on_match, L3 auto-delivery proceeds only when all required application_requirements are satisfiable (else the candidate is invited to apply first). Backward-compatible: absent = no application requirements. Foreign agents that do not understand this field MUST treat it as absent (no extra requirements).

# properties.application_requirements.items.properties.key.description
Stable identifier for this requirement (often equals type).

# properties.application_requirements.items.properties.type.description
Kind of material requested. Note: identity-bearing types (linkedin, portfolio) are still L3 content like every other requirement — there is NO tier field; all requirement content is L3 by nature.

# properties.application_requirements.items.properties.importance.description
required = match cannot complete without it (invites apply if missing). wished = requested, never blocks. supporting = nice-to-have, never blocks.

# properties.application_requirements.items.properties.prompt.description
Optional human-facing prompt shown to the applicant for this requirement.

# properties.l2_delivery_channel.description
How L2 payloads are delivered to the vacancy poster.

# properties.l2_delivery_target.description
Email address or webhook URL depending on l2_delivery_channel. Withheld from public card surface.

# properties.l3_intake_mode.description
How this vacancy takes identity (L3) once a candidate is L2-eligible. on_request = screened intake (today's model): receive L2, a human reviews and requests L3 per candidate. on_match = open intake: the L3 of any L2-eligible candidate whose consent permits it is auto-delivered to the vacancy's L3 endpoint. The vacancy preference NEVER overrides the seeker — a seeker who has not consented to auto-disclose is still asked (signal l3_initiated). Backward-compatible: absent = on_request, so existing v0.2 cards behave unchanged. Foreign agents that do not understand this field MUST treat it as on_request.

# properties.verification.description
How the vacancy account behind this card was verified.

# properties.canonical_url.description
For mirror cards, the original third-party URL. For primary cards, the canonical card URL.

# properties.source_provider.description
For mirror cards, the source provider name (e.g., 'linkedin', 'indeed'). Not present on primary cards.

# properties.takedown_url.description
Optional GDPR/erasure affordance — where an applicant, seeker agent, or data subject can request takedown or erasure of this posting and any data derived from interacting with it. Backward-compatible: absent = no machine-readable takedown endpoint advertised. Foreign agents that do not understand this field MUST treat it as absent.