A (hopefully comprehensive) versioning proposal

After much discussion, both in email and at the most recent telcon
(http://www.w3.org/XML/XProc/2009/10/15-minutes), it seems that we
need to consider a radical overhaul of our versioning strategy.

============================================================

Problem statement:

Following our telcon, at a high level, we want to:

1. Remove the requirement to load step declarations

Consensus appears to be that it's too great a burden on users to
require explicit declarations of 2.0 step types to be available to a
1.0 processor when evaluating a 2.0 pipeline.

2. Add some sort of version attribute

Satisfying the first requirement is going to require relaxing some of
the rules about static checking of the dependency graph. This must
only apply when forwards-compatible processing is explicitly enabled.
A 1.0 processor looking at a 1.0 pipeline must be able to report
errors in the structure of the pipeline statically.

3. Add defaulting rules that will allow dynamic selection
   (p:choose/p:try) of steps in a backwards compatible way.

It should be possible to write a pipeline that will use a V2.0 step in
a 2.0 processor and do something else in a V1.0 processor. It ought to
be possible to make this choice at runtime using p:choose or p:try.

This means that a 1.0 processor operating in forwards-compatible mode
must be able to build a dependency graph for a pipeline that includes
steps it does not recognize.

4. Add something like XSLT's use-when so that authors can make
   explicit versioning choices based on statically available
   information (like the processor version)

It seems unlikely that it will be possible to handle every possible
future change with defaulting rules. In particular, the addition of a
new compound step like p:map-reduce might introduce too much
complexity for a 1.0 processor to "parse around" even in a conditional
branch.

Adding use-when gives the pipeline author complete control over what
the 1.0 processor sees. In effect, armed with item 4, an author can
always write a pipeline that will work in 1.0 without any new
defaulting rules. So with use-when, the additional defaulting rules
described above are unnecessary. But for the moment, I'm operating on
the assumption that we might like some pipelines to work in a 1.0
processor even without making explicit use of use-when.

============================================================

Proposal 1: Add a version attribute.

The version attribute is allowed on any element. On an element in the
XProc namespace, it is "version" in no namespace. On an element not in
the XProc namespace, it is "p:version" in the XProc namespace.

The version value applies to the element on which it occurs and all
descendant elements unless or until overriden by another explicit
version setting.

On an element with an explicit version greater than the highest
version known by the processor, forwards-compatible behavior is
enabled.

============================================================

Proposal 2: The version attribute is required on top-level
p:pipeline, p:declare-step, and p:library elements.

The current XProc specification defines the semantics of
version "1.0".

============================================================

Proposal 3: Add a use-when attribute

The use-when attribute is allowed on any element. On an element in the
XProc namespace, it is "use-when" in no namespace. On an element not in
the XProc namespace, it is "p:use-when" in the XProc namespace.

The value of the use-when attribute is evaluted as an XPath expression
with an undefined context node. It is a static error if the expression
makes reference to anything in the dynamic context.

Conceptually, this expression is evaluated while the pipeline is being
loaded. If the effective boolean value of the expression in the
use-when attribute is false, the element on which it occurs and all
the descendants of that element are removed from the pipeline. 

It is as if they never occurred and they are not considered in any
further static analysis or dynamic evaluation.

============================================================

Proposal 3: Attempt to treate unknown XProc step types as dynamic errors

Pipelines that attempt to unconditionally evaluate unknown step types
are guaranteed to fail. It's possible that some new XProc steps may
have attributes or children that a previous processor could not be
expected to interpret.

In order to minimize the disruption such elements cause, while at the
same time making it as easy as possible to write pipelines that will
run in fowards-compatible mode on an earlier processor, we adopt the
following rules:

In forwards-compatible mode:

Steps in the XProc namespace which the processor does not recognize
are marked invalid. It is a dynamic error (err:XD00??) to attempt to
evaluate a step that is marked invalid.

Any subpipeline that directly contains a step which has been marked
invalid is also invalid.

If a p:when directly contains an unknown XProc step type, that entire
branch is marked invalid. No static analysis is performed on the steps
within that pipeline. It is a dynamic error (err:XD00??) if that
branch of the p:choose is selected at runtime.

If a p:group inside a p:try directly contains an unknown XProc step
type, that group is marked invalid. No static analysis is performed on
the steps within that group. An attempt to evaluate the p:try will
immediately evaluate the p:catch with an empty errors port.

If all of the branches of a p:choose are marked invalid, then the
entire p:choose is invalid. If both the p:group and p:catch branches
of a p:try are invalid, then the entire p:try is invalid.

If all of the branches of a p:choose or p:try are invalid, the
p:choose or p:try is treated as if it had no primary output port.

============================================================

Proposal 4: Support binding defaults in forwards-compatible mode

In forwards-compatible mode:

It is not a static error to encounter an unknown XProc step type. Such
steps are assumed to have no primary input ports, no primary output
ports, and no parameter input ports. It is a static error if, under
these assumptions, a valid dependency graph cannot be constructed.

It is not a static error to encounter explicit bindings to unknown
ports on an XProc step type. Such bindings are assumed to be correct.
Dynamically, a step must ignore all inputs that appear on an unknown
input port and must produce an empty sequence of documents on unknown
output ports.

============================================================

Examples:

A. No version attribute.

The following pipeline is statically invalid because it does not
have a top-level version attribute.

  <p:pipeline>
    <p:identity/>
  </p:pipeline>

B. Forwards-compatible mode

The following pipeline runs in forwards-compatible mode.

  <p:pipeline version="2.0">
    <p:identity/>
  </p:pipeline>

C. Use-when case 1

From the perspective of a 1.0 processor, the following pipeline:

  <p:pipeline version="2.0">
    <p:identity2 use-when="system-property('p:version') &gt; 1.0"/>
    <p:identity use-when="system-property('p:version') &lt;= 1.0"/>
  </p:pipeline>

behaves exactly as if it was written thus:

  <p:pipeline version="2.0">
    <p:identity/>
  </p:pipeline>

From the perspective of a 2.0 processor, thus:

  <p:pipeline version="2.0">
    <p:identity2/>
  </p:pipeline>

D. Use-when case 2

From the perspective of a 1.0 processor, the following pipeline:

  <p:pipeline version="2.0">
    <p:import use-when="system-property('p:version') &gt; 1.0" href="v2.xpl"/>
    <p:import use-when="system-property('p:version') &lt;= 1.0" href="v1.xpl"/>
    <p:pipeinfo>
      <cfg:value-1 p:use-when="system-property('p:version') &lt;= 1.0"/>
      <cfg:value-2 p:use-when="system-property('p:version') &gt; 1.0"/>
    </p:pipeinfo>

    <p:xslt name="xslt">
      <p:input port="stylesheet">...</p:input>
    </p:xslt>

    <p:sink/>

    <p:identity>
      <p:input port="source"
               use-when="system-property('p:version') &gt; 1.0">
        <p:pipe step="xslt" port="messages"/>
      </p:input>
      <p:input port="source"
               use-when="system-property('p:version') &lt;= 1.0">
        <p:empty/>
      </p:input>
    </p:identity>
  </p:pipeline>

behaves exactly as if it was written thus:

  <p:pipeline version="2.0">
    <p:import href="v1.xpl"/>
    <p:pipeinfo>
      <cfg:value-1/>
    </p:pipeinfo>

    <p:xslt name="xslt">
      <p:input port="stylesheet">...</p:input>
    </p:xslt>

    <p:sink/>

    <p:identity>
      <p:input port="source">
        <p:empty/>
      </p:input>
    </p:identity>
  </p:pipeline>

From the perspective of a 2.0 processor, thus:

  <p:pipeline version="2.0">
    <p:import href="v2.xpl"/>
    <p:pipeinfo>
      <cfg:value-2/>
    </p:pipeinfo>

    <p:xslt name="xslt">
      <p:input port="stylesheet">...</p:input>
    </p:xslt>

    <p:sink/>

    <p:identity>
      <p:input port="source">
        <p:pipe step="xslt" port="messages"/>
      </p:input>
    </p:identity>
  </p:pipeline>

E. Dynamic errors 1

The following pipeline is statically valid to an XProc 1.0 processor
and will run without errors.

  <p:pipeline version="2.0">

    <p:choose>
      <p:when test="system-property('p:version') &gt; 1.0">
        <p:fribble/>
        <p:sink/>
      </p:when>
      <p:otherwise>
        <p:identity/>
      </p:otherwise>
    </p:choose>

  </pipeline>

The first p:when branch is marked invalid because it contains the
unknown (from the 1.0 perspective) step p:fribble. As a result, no
static analysis is performed on that subpipeline and that subpipeline
does not contribute to the constraint that all the branches of a
p:choose must declare the same outputs.

F. Dynamic errors 2

The following pipeline is statically valid to an XProc 1.0 processor
but will fail at runtime:

  <p:pipeline version="2.0">

    <p:choose>
      <p:when test="true()">
        <p:fribble/>
        <p:sink/>
      </p:when>
      <p:otherwise>
        <p:identity/>
      </p:otherwise>
    </p:choose>

  </pipeline>

A processor may reject this statically, but is not required to do so.

G. Binding defaults 1

The following pipeline is valid and will run forwards-compatible mode:

  <p:pipeline version="2.0">
    <p:xslt name="xslt">
      <p:input port="stylesheet">...</p:input>
    </p:xslt>

    <p:sink/>

    <p:identity>
      <p:input port="source">
        <p:pipe step="xslt" port="messages"/>
      </p:input>
    </p:identity>
  </p:pipeline>

It will always produce an empty sequence.

H. Binding defaults 2

The following subpipeline has a well-defined dependency graph, but
will be marked invalid:

  <p:group>
    <p:output port="result">
      <p:pipe step="newv2" port="result"/>
    </p:output>

    <p:unknown-type name="newv2"/>
  </p:group>

I. Binding defaults 3

This pipeline is statically invalid.

  <p:group>
    <p:output port="result"/>
    <p:unknown-type name="newv2"/>
  </p:group>

In "H", the explicit binding to the unknown port is not an error. In
the this case, the default binding for the output port 'result' is the
primary output port of the last step in the subpipeline, however, as
far as the V1.0 processor knows, the last step does not have a primary
output port.

J. Static errors 1

This pipeline is statically valid but will not run

  <p:declare-step version="2.0">
    <p:unknown-type/>
  </p:declare-step>

K. Static errors 2

This pipeline is statically *in*valid:

  <p:pipeline version="2.0">
    <p:choose>
      <p:when test="system-property('p:version') &gt; 1.0">
        <p:unknown/>
      </p:when>
      <p:otherwise>
        <p:unknown/>
      </p:otherwise>
    </p:choose>
  </p:pipeline>

There's nothing for the p:output on the p:pipeline to bind to by
default.

L. Static errors 3

This pipeline is statically valid:

  <p:declare-step version="2.0">
    <p:choose>
      <p:when test="system-property('p:version') &gt; 1.0">
        <p:unknown/>
      </p:when>
      <p:otherwise>
        <p:unknown/>
      </p:otherwise>
    </p:choose>
  </p:declar-step>

Though it will necessary fail without running any steps.

I haven't attempted to propose what a 1.0 processor should do if the
2.0 step declarations are loaded and they differ from what it expected
in 1.0.

It is still an error to explicitly load the 1.0 declarations and the
2.0 declarations.

I haven't thought through the implications of mixing 1.0 and 2.0
pipelines together. Presumably it all "just works" but I'm not
cheerful about the prospect of implmenting that. :-/

                                        Be seeing you,
                                          norm

-- 
Norman Walsh <ndw@nwalsh.com> | It's a poor sort of memory that only
http://nwalsh.com/            | works backward.--Lewis Carroll

Received on Thursday, 15 October 2009 18:58:47 UTC